├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE.rst ├── README.rst ├── protocol.rst ├── src ├── errors.rs ├── index.rs ├── lib.rs ├── lock.rs ├── main.rs ├── msg.rs ├── msgmacros.rs ├── pool.rs ├── reader.rs ├── records.rs ├── server.rst ├── storage.rs ├── storage.rst ├── tid.rs ├── transaction.rs ├── util.rs └── writer.rs ├── tests ├── reader.rs ├── storage.rs └── writer.rs └── to-do.rst /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.45" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "ee10e43ae4a853c0a3591d4e2ada1719e553be18199d9da9d4a83f5927c2f5c7" 10 | 11 | [[package]] 12 | name = "autocfg" 13 | version = "1.0.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 16 | 17 | [[package]] 18 | name = "byteorder" 19 | version = "0.4.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "96c8b41881888cc08af32d47ac4edd52bc7fa27fef774be47a92443756451304" 22 | 23 | [[package]] 24 | name = "byteorder" 25 | version = "0.5.3" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" 28 | 29 | [[package]] 30 | name = "byteserver" 31 | version = "0.1.0" 32 | dependencies = [ 33 | "anyhow", 34 | "byteorder 0.5.3", 35 | "itertools", 36 | "memmap", 37 | "pipe", 38 | "rmp", 39 | "rmp-serde", 40 | "serde", 41 | "tempdir", 42 | "tempfile", 43 | "thiserror", 44 | "time", 45 | ] 46 | 47 | [[package]] 48 | name = "cfg-if" 49 | version = "0.1.10" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 52 | 53 | [[package]] 54 | name = "crossbeam-channel" 55 | version = "0.4.2" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" 58 | dependencies = [ 59 | "crossbeam-utils", 60 | "maybe-uninit", 61 | ] 62 | 63 | [[package]] 64 | name = "crossbeam-utils" 65 | version = "0.7.2" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 68 | dependencies = [ 69 | "autocfg", 70 | "cfg-if", 71 | "lazy_static", 72 | ] 73 | 74 | [[package]] 75 | name = "either" 76 | version = "1.5.3" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" 79 | 80 | [[package]] 81 | name = "fs2" 82 | version = "0.2.5" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "bcd414e5a1a979b931bb92f41b7a54106d3f6d2e6c253e9ce943b7cd468251ef" 85 | dependencies = [ 86 | "kernel32-sys", 87 | "libc", 88 | "winapi 0.2.8", 89 | ] 90 | 91 | [[package]] 92 | name = "fuchsia-cprng" 93 | version = "0.1.1" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 96 | 97 | [[package]] 98 | name = "itertools" 99 | version = "0.5.10" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "4833d6978da405305126af4ac88569b5d71ff758581ce5a987dbfa3755f694fc" 102 | dependencies = [ 103 | "either", 104 | ] 105 | 106 | [[package]] 107 | name = "kernel32-sys" 108 | version = "0.2.2" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 111 | dependencies = [ 112 | "winapi 0.2.8", 113 | "winapi-build", 114 | ] 115 | 116 | [[package]] 117 | name = "lazy_static" 118 | version = "1.4.0" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 121 | 122 | [[package]] 123 | name = "libc" 124 | version = "0.2.69" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" 127 | 128 | [[package]] 129 | name = "maybe-uninit" 130 | version = "2.0.0" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 133 | 134 | [[package]] 135 | name = "memmap" 136 | version = "0.4.0" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "69253224aa10070855ea8fe9dbe94a03fc2b1d7930bb340c9e586a7513716fea" 139 | dependencies = [ 140 | "fs2", 141 | "kernel32-sys", 142 | "libc", 143 | "winapi 0.2.8", 144 | ] 145 | 146 | [[package]] 147 | name = "pipe" 148 | version = "0.3.0" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "bcd11e042e056991b5df9c0c5ae6bd0cce219b74294c40f65b89f40f7030106c" 151 | dependencies = [ 152 | "crossbeam-channel", 153 | ] 154 | 155 | [[package]] 156 | name = "proc-macro2" 157 | version = "1.0.32" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" 160 | dependencies = [ 161 | "unicode-xid", 162 | ] 163 | 164 | [[package]] 165 | name = "quote" 166 | version = "1.0.10" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 169 | dependencies = [ 170 | "proc-macro2", 171 | ] 172 | 173 | [[package]] 174 | name = "rand" 175 | version = "0.3.23" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" 178 | dependencies = [ 179 | "libc", 180 | "rand 0.4.6", 181 | ] 182 | 183 | [[package]] 184 | name = "rand" 185 | version = "0.4.6" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" 188 | dependencies = [ 189 | "fuchsia-cprng", 190 | "libc", 191 | "rand_core 0.3.1", 192 | "rdrand", 193 | "winapi 0.3.8", 194 | ] 195 | 196 | [[package]] 197 | name = "rand_core" 198 | version = "0.3.1" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 201 | dependencies = [ 202 | "rand_core 0.4.2", 203 | ] 204 | 205 | [[package]] 206 | name = "rand_core" 207 | version = "0.4.2" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 210 | 211 | [[package]] 212 | name = "rdrand" 213 | version = "0.4.0" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 216 | dependencies = [ 217 | "rand_core 0.3.1", 218 | ] 219 | 220 | [[package]] 221 | name = "redox_syscall" 222 | version = "0.1.56" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 225 | 226 | [[package]] 227 | name = "remove_dir_all" 228 | version = "0.5.2" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" 231 | dependencies = [ 232 | "winapi 0.3.8", 233 | ] 234 | 235 | [[package]] 236 | name = "rmp" 237 | version = "0.7.5" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "a2da68cc45d803dfd68724d767363d82c6f76293a2bf5fe6ded34f640ee01447" 240 | dependencies = [ 241 | "byteorder 0.4.2", 242 | ] 243 | 244 | [[package]] 245 | name = "rmp-serde" 246 | version = "0.10.0" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "b371d26290d86dfa2716f1b0878a3832b94d3803fdc402a1697440d86ddbe602" 249 | dependencies = [ 250 | "rmp", 251 | "serde", 252 | ] 253 | 254 | [[package]] 255 | name = "serde" 256 | version = "0.8.23" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" 259 | 260 | [[package]] 261 | name = "syn" 262 | version = "1.0.81" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" 265 | dependencies = [ 266 | "proc-macro2", 267 | "quote", 268 | "unicode-xid", 269 | ] 270 | 271 | [[package]] 272 | name = "tempdir" 273 | version = "0.3.7" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" 276 | dependencies = [ 277 | "rand 0.4.6", 278 | "remove_dir_all", 279 | ] 280 | 281 | [[package]] 282 | name = "tempfile" 283 | version = "2.2.0" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "11ce2fe9db64b842314052e2421ac61a73ce41b898dc8e3750398b219c5fc1e0" 286 | dependencies = [ 287 | "kernel32-sys", 288 | "libc", 289 | "rand 0.3.23", 290 | "redox_syscall", 291 | "winapi 0.2.8", 292 | ] 293 | 294 | [[package]] 295 | name = "thiserror" 296 | version = "1.0.30" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" 299 | dependencies = [ 300 | "thiserror-impl", 301 | ] 302 | 303 | [[package]] 304 | name = "thiserror-impl" 305 | version = "1.0.30" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" 308 | dependencies = [ 309 | "proc-macro2", 310 | "quote", 311 | "syn", 312 | ] 313 | 314 | [[package]] 315 | name = "time" 316 | version = "0.1.43" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" 319 | dependencies = [ 320 | "libc", 321 | "winapi 0.3.8", 322 | ] 323 | 324 | [[package]] 325 | name = "unicode-xid" 326 | version = "0.2.2" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 329 | 330 | [[package]] 331 | name = "winapi" 332 | version = "0.2.8" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 335 | 336 | [[package]] 337 | name = "winapi" 338 | version = "0.3.8" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 341 | dependencies = [ 342 | "winapi-i686-pc-windows-gnu", 343 | "winapi-x86_64-pc-windows-gnu", 344 | ] 345 | 346 | [[package]] 347 | name = "winapi-build" 348 | version = "0.1.1" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 351 | 352 | [[package]] 353 | name = "winapi-i686-pc-windows-gnu" 354 | version = "0.4.0" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 357 | 358 | [[package]] 359 | name = "winapi-x86_64-pc-windows-gnu" 360 | version = "0.4.0" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 363 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "byteserver" 3 | version = "0.1.0" 4 | authors = ["Jim Fulton "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | anyhow = "1.0" 9 | byteorder = "0.5.3" 10 | itertools = "0.5.2" 11 | memmap = "0.4.0" 12 | rmp = "0.7.5" 13 | rmp-serde = "0.10.0" 14 | serde = "0.8.12" 15 | tempdir = "0.3.5" 16 | tempfile = "2.1.4" 17 | thiserror = "1.0" 18 | time = "0.1.35" 19 | 20 | [dev-dependencies] 21 | pipe = "0.3.0" 22 | 23 | [profile.release] 24 | debug = true 25 | -------------------------------------------------------------------------------- /LICENSE.rst: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Jim Fulton 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ================================================= 2 | Experimental Fast ZEO/ZODB server written in Rust 3 | ================================================= 4 | 5 | The main goal of this project is to provide a fast `ZEO 6 | `_ server for `ZODB 7 | `_. 8 | 9 | This is in an early stage of development. 10 | 11 | Unlike ZEO, this server treats the data it stores as opaque, as a result, it: 12 | 13 | - Requires clients to provide conflict resolution. 14 | 15 | - Requires use of an external garbage collector. 16 | 17 | - Does not support undo (although it might support undo without 18 | conflict resolution later). 19 | 20 | -------------------------------------------------------------------------------- /protocol.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | Byteserver protocol 3 | =================== 4 | 5 | Wire 6 | ==== 7 | 8 | Sequence of sized messages: 9 | 10 | - big-endian 32-bit message size 11 | 12 | - Data 13 | 14 | First message is protocol identifier. 15 | 16 | Remaining messages are msgpack-encoded triples: 17 | 18 | message id 19 | -1 => heartbeat 20 | 0 => async message, no reply should be sent. 21 | >0 => id to send response with. 22 | 23 | method 24 | 'E' => message is an error result 25 | 'R' => message is a normal reply 26 | 27 | payload 28 | request => tuple of arguments 29 | error => tuple of error name and error data, where data is a tuplle 30 | of values. 31 | response => returned value 32 | 33 | Methods 34 | ======= 35 | 36 | Methods are synchronous unless otherwise noted. 37 | 38 | register(storage, read_only) 39 | Register to use a particular storage. 40 | 41 | This must be the first message sent. 42 | 43 | It returns the last committed transaction id. 44 | 45 | loadBefore(oid, tid) 46 | Load the value for oid committed before Tid. 47 | 48 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | 2 | #[derive(thiserror::Error, Debug)] 3 | pub enum POSError { 4 | #[error("ZODB.POSException.POSKeyError")] 5 | Key([u8;8]), 6 | } 7 | -------------------------------------------------------------------------------- /src/index.rs: -------------------------------------------------------------------------------- 1 | // File-storage index-file and mmap index 2 | use std::io::prelude::*; 3 | 4 | use byteorder::{ReadBytesExt, WriteBytesExt}; 5 | 6 | use crate::util; 7 | 8 | pub type Index = std::collections::btree_map::BTreeMap; 9 | 10 | static MAGIC: &'static [u8] = b"fs2i"; 11 | 12 | pub fn save_index(index: &Index, path: &str, 13 | segment_size: u64, start: &util::Tid, end: &util::Tid) 14 | -> std::io::Result<()> { 15 | let mut writer = std::io::BufWriter::new(std::fs::File::create(path)?); 16 | writer.write_all(MAGIC)?; 17 | writer.write_u64::(index.len() as u64)?; 18 | writer.write_u64::(segment_size)?; 19 | writer.write_all(start)?; 20 | writer.write_all(end)?; 21 | for (key, value) in index.iter() { 22 | writer.write_all(key)?; 23 | writer.write_u64::(*value)?; 24 | } 25 | Ok(()) 26 | } 27 | 28 | pub fn load_index(path: &str) -> std::io::Result<(Index, u64, util::Tid, util::Tid)> { 29 | let mut reader = std::io::BufReader::new(std::fs::File::open(path)?); 30 | util::check_magic(&mut reader, MAGIC)?; 31 | let index_length = reader.read_u64::()?; 32 | let segment_size = reader.read_u64::()?; 33 | let start = util::read8(&mut reader)?; 34 | let end = util::read8(&mut reader)?; 35 | let mut index = Index::new(); 36 | for i in 0..index_length { 37 | index.insert(util::read8(&mut reader)?, 38 | reader.read_u64::()?); 39 | } 40 | Ok((index, segment_size, start, end)) 41 | } 42 | 43 | // ====================================================================== 44 | 45 | #[cfg(test)] 46 | mod tests { 47 | 48 | use super::*; 49 | use crate::util; 50 | 51 | #[test] 52 | fn works() { 53 | let mut index = Index::new(); 54 | 55 | for i in 0..10 { 56 | index.insert(util::p64(i), i*999); 57 | } 58 | 59 | let tmpdir = util::test::dir(); 60 | 61 | let path = String::from(tmpdir.path().join("index").to_str().unwrap()); 62 | let segment_size = 9999u64; 63 | let start = util::p64(1); 64 | let end = util::p64(1234567890); 65 | 66 | save_index(&index, &path, segment_size, &start, &end).unwrap(); 67 | 68 | assert_eq!(load_index(&path).unwrap(), 69 | (index, segment_size, start, end)); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "1024"] 2 | #![allow(dead_code, unused_must_use, unused_variables)] 3 | 4 | extern crate byteorder; 5 | pub extern crate rmp; 6 | pub extern crate rmp_serde; 7 | extern crate serde; 8 | extern crate tempdir; 9 | extern crate tempfile; 10 | extern crate time; 11 | 12 | #[macro_use] 13 | pub mod util; 14 | 15 | #[macro_use] 16 | pub mod msgmacros; 17 | 18 | pub mod errors; 19 | pub mod storage; 20 | mod index; 21 | mod lock; 22 | pub mod msg; 23 | mod pool; 24 | mod records; 25 | pub mod reader; 26 | pub mod writer; 27 | pub mod tid; 28 | mod transaction; 29 | -------------------------------------------------------------------------------- /src/lock.rs: -------------------------------------------------------------------------------- 1 | 2 | use crate::util; 3 | 4 | pub struct Locking { 5 | id: util::Tid, 6 | want: Vec, 7 | got: Vec, 8 | locked: Box, 9 | } 10 | 11 | pub struct LockManager { 12 | locks: std::collections::HashSet, 13 | waiting: std::collections::HashMap 17 | >, 18 | locking: std::collections::HashMap, 19 | } 20 | 21 | impl LockManager { 22 | 23 | pub fn new() -> LockManager { 24 | LockManager { 25 | locks: std::collections::HashSet::new(), 26 | waiting: std::collections::HashMap::new(), 27 | locking: std::collections::HashMap::new(), 28 | } 29 | } 30 | 31 | pub fn lock(&mut self, 32 | id: util::Tid, 33 | want: Vec, 34 | locked: Box, 35 | ) { 36 | self.lock_waiting( 37 | Locking { id: id, want: want, got: vec![], locked: locked }); 38 | } 39 | 40 | fn lock_waiting(&mut self, mut locking: Locking) { 41 | let id = locking.id; 42 | { // Limit lifetime of locker borrow below :( 43 | let want = &mut locking.want; 44 | let got = &mut locking.got; 45 | while ! want.is_empty() { 46 | let oid = want.last().unwrap().clone(); 47 | if self.locks.contains(&oid) { 48 | if self.waiting.contains_key(&oid) { 49 | self.waiting.get_mut(&oid).unwrap().push_back(id); 50 | } 51 | else { 52 | let mut waiting: 53 | std::collections::vec_deque::VecDeque = 54 | std::collections::vec_deque::VecDeque::new(); 55 | waiting.push_back(id); 56 | self.waiting.insert(oid, waiting); 57 | } 58 | break; 59 | } 60 | else { 61 | self.locks.insert(oid); 62 | got.push(want.pop().unwrap()); 63 | } 64 | } 65 | if want.is_empty() { 66 | (*locking.locked)(locking.id) 67 | } 68 | } 69 | self.locking.insert(id, locking); 70 | 71 | } 72 | 73 | pub fn release(&mut self, id: &util::Tid) { 74 | // Release any locks held for the given id. This has no effect of no 75 | // locks are held. 76 | if let Some(mut locking) = self.locking.remove(id) { 77 | while ! locking.got.is_empty() { 78 | let oid = locking.got.pop().unwrap(); 79 | self.locks.remove(&oid); 80 | if self.waiting.contains_key(&oid) { 81 | let tid_waiting = 82 | self.waiting.get_mut(&oid).unwrap().pop_front(); 83 | if self.waiting.get(&oid).unwrap().is_empty() { 84 | self.waiting.remove(&oid); 85 | } 86 | if let Some(tid) = tid_waiting { 87 | if let Some(locking) = self.locking.remove(&tid) { 88 | self.lock_waiting(locking); 89 | } 90 | } 91 | } 92 | } 93 | } 94 | } 95 | } 96 | 97 | 98 | // ====================================================================== 99 | 100 | #[cfg(test)] 101 | mod tests { 102 | 103 | use super::*; 104 | 105 | struct TestLocker { id: util::Tid, pub is_locked: bool } 106 | impl TestLocker { 107 | fn locked(&mut self) { self.is_locked = true; } 108 | } 109 | fn newt(id: u64) -> util::Ob { 110 | util::new_ob(TestLocker {id: util::p64(id), is_locked: false}) 111 | } 112 | fn oids(v: Vec) -> Vec { 113 | v.iter().map(| i | util::p64(*i)).collect::>() 114 | } 115 | fn lock(lm: &mut LockManager, locker: util::Ob, oids: Vec) { 116 | let id = locker.borrow().id; 117 | let orig_id = id.clone(); 118 | lm.lock(id, 119 | oids.iter().map(| i | util::p64(*i)).collect::>(), 120 | Box::new(move | lid | { 121 | assert_eq!(lid, orig_id); 122 | locker.borrow_mut().locked() 123 | }), 124 | ) 125 | } 126 | 127 | #[test] 128 | fn works() { 129 | let mut lm = LockManager::new(); 130 | 131 | let l1_123 = newt(1); 132 | lock(&mut lm, l1_123.clone(), vec![1, 2, 3]); 133 | assert!(l1_123.borrow().is_locked); 134 | 135 | let l2_12 = newt(2); 136 | let l3_12 = newt(3); 137 | let l4_3 = newt(4); 138 | lock(&mut lm, l2_12.clone(), vec![1, 2]); 139 | lock(&mut lm, l3_12.clone(), vec![1, 2]); 140 | lock(&mut lm, l4_3.clone(), vec![3]); 141 | assert!( l1_123.borrow().is_locked); 142 | assert!(! l2_12.borrow().is_locked); 143 | assert!(! l3_12.borrow().is_locked); 144 | assert!(! l4_3.borrow().is_locked); 145 | 146 | let l5_4 = newt(5); 147 | lock(&mut lm, l5_4.clone(), vec![4]); 148 | assert!( l1_123.borrow().is_locked); 149 | assert!(! l2_12.borrow().is_locked); 150 | assert!(! l3_12.borrow().is_locked); 151 | assert!(! l4_3.borrow().is_locked); 152 | assert!( l5_4.borrow().is_locked); 153 | 154 | lm.release(&util::p64(1)); 155 | assert!( l2_12.borrow().is_locked); 156 | assert!(! l3_12.borrow().is_locked); 157 | assert!( l4_3.borrow().is_locked); 158 | assert!( l5_4.borrow().is_locked); 159 | 160 | lm.release(&util::p64(2)); 161 | assert!( l3_12.borrow().is_locked); 162 | assert!( l4_3.borrow().is_locked); 163 | assert!( l5_4.borrow().is_locked); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate byteserver; 2 | 3 | fn main() { 4 | 5 | // TODO, options :) 6 | let fs = std::sync::Arc::new( 7 | byteserver::storage::FileStorage::::open( 8 | String::from("data.fs")).unwrap()); 9 | 10 | let listener = std::net::TcpListener::bind("127.0.0.1:8080").unwrap(); 11 | 12 | for stream in listener.incoming() { 13 | match stream { 14 | Ok(stream) => { 15 | stream.set_nodelay(true).unwrap(); 16 | println!("Accepted {:?} {}", stream, stream.nodelay().unwrap()); 17 | let (send, receive) = std::sync::mpsc::channel(); 18 | 19 | let client = byteserver::writer::Client::new( 20 | stream.peer_addr().unwrap().to_string(), send.clone()); 21 | fs.add_client(client.clone()); 22 | 23 | let read_fs = fs.clone(); 24 | let read_stream = stream.try_clone().unwrap(); 25 | std::thread::spawn( 26 | move || 27 | byteserver::reader::reader( 28 | read_fs, read_stream, send).unwrap()); 29 | 30 | let write_fs = fs.clone(); 31 | std::thread::spawn( 32 | move || 33 | byteserver::writer::writer( 34 | write_fs, stream, receive, client).unwrap()); 35 | }, 36 | Err(e) => { println!("WTF {}", e) } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/msg.rs: -------------------------------------------------------------------------------- 1 | 2 | use byteorder::{BigEndian, ByteOrder}; 3 | 4 | use serde::bytes::ByteBuf; 5 | 6 | use anyhow::{anyhow, Context, Result}; 7 | 8 | use crate::util; 9 | use crate::msgmacros::*; 10 | 11 | pub fn size_vec(mut v: Vec) -> Vec { 12 | let l = v.len(); 13 | for i in 0..4 { 14 | v.insert(0, 0); 15 | } 16 | BigEndian::write_u32(&mut v, l as u32); 17 | v 18 | } 19 | 20 | pub const NIL: Option = None; 21 | 22 | pub fn bytes(data: &[u8]) -> serde::bytes::Bytes { 23 | serde::bytes::Bytes::new(data) 24 | } 25 | 26 | #[derive(Debug, PartialEq)] 27 | pub enum Zeo { 28 | Raw(Vec), 29 | End, 30 | 31 | Register(i64, String, bool), 32 | LoadBefore(i64, util::Oid, util::Tid), 33 | GetInfo(i64), 34 | NewOids(i64), 35 | TpcBegin(u64, util::Bytes, util::Bytes, util::Bytes), 36 | Storea(util::Oid, util::Tid, util::Bytes, u64), 37 | Vote(i64, u64), 38 | TpcFinish(i64, u64), 39 | TpcAbort(i64, u64), 40 | Ping(i64), 41 | 42 | Locked(i64, u64), 43 | 44 | Finished(i64, util::Tid, u64, u64), 45 | Invalidate(util::Tid, Vec), 46 | } 47 | 48 | pub struct ZeoIter { 49 | reader: T, 50 | buf: [u8; 1<<16], 51 | input: Vec, 52 | } 53 | 54 | static HEARTBEAT_PREFIX: [u8; 2] = [147, 255]; 55 | 56 | impl ZeoIter { 57 | 58 | pub fn new(reader: T) -> ZeoIter { 59 | ZeoIter { reader: reader, buf: [0u8; 1<<16], input: vec![] } 60 | } 61 | 62 | fn read_want(&mut self, want: usize) -> Result { 63 | while self.input.len() < want { 64 | let n = self.reader.read(&mut self.buf).context("reading")?; 65 | if n > 0 { 66 | self.input.extend_from_slice(&self.buf[..n]); 67 | } 68 | else { 69 | return Ok(true); 70 | } 71 | } 72 | Ok(false) 73 | } 74 | 75 | fn advance(&mut self) -> Result { 76 | Ok( 77 | if self.read_want(4)? { 0 } 78 | else { 79 | let want = (BigEndian::read_u32(&self.input) + 4) as usize; 80 | if self.read_want(want)? { 0 } 81 | else { want } 82 | } 83 | ) 84 | } 85 | 86 | pub fn next_vec(&mut self) -> Result> { 87 | let want = self.advance()?; 88 | let mut data = self.input.split_off(want as usize); 89 | std::mem::swap(&mut data, &mut self.input); 90 | Ok(data.split_off(4)) 91 | } 92 | 93 | pub fn next(&mut self) -> Result { 94 | let want = self.advance()?; 95 | if want == 0 { 96 | return Ok(Zeo::End); 97 | } 98 | let mut data = self.input.split_off(want as usize); 99 | std::mem::swap(&mut data, &mut self.input); 100 | 101 | if data[4..6] == HEARTBEAT_PREFIX { 102 | return self.next() // skip heartbeats 103 | } 104 | //println!("Read vec {:?}", &data[4..]); 105 | let mut reader = std::io::Cursor::new(data.split_off(4)); 106 | parse_message(&mut reader) 107 | } 108 | 109 | } 110 | 111 | fn pre_parse(mut reader: &mut dyn std::io::Read) 112 | -> Result<(i64, String)> { 113 | let array_size = 114 | rmp::decode::read_array_size(&mut reader).context("get mess size")?; 115 | if array_size != 3 { 116 | return Err(anyhow!("Invalid message size. Expect 3, got {}", array_size))?; 117 | } 118 | let id: i64 = decode!(&mut reader, "decoding message id")?; 119 | let method: String = decode!(&mut reader, "decoding message name")?; 120 | Ok((id, method)) 121 | } 122 | 123 | fn parse_message(mut reader: &mut dyn std::io::Read) -> Result { 124 | let (id, method) = pre_parse(&mut reader)?; 125 | 126 | Ok(match method.as_ref() { 127 | "loadBefore" => { 128 | let (oid, before): (ByteBuf, ByteBuf) = 129 | decode!(&mut reader, "decoding loadBefore oid")?; 130 | let oid = util::read8(&mut (&*oid)).context("loadBefore oid")?; 131 | let before = 132 | util::read8(&mut (&*before)) 133 | .context("loadBefore before")?; 134 | Zeo::LoadBefore(id, oid, before) 135 | }, 136 | "ping" => Zeo::Ping(id), 137 | "tpc_begin" => { 138 | let (txn, user, desc, ext, _, _): ( 139 | u64, ByteBuf, ByteBuf, ByteBuf, Option, ByteBuf) = 140 | decode!(&mut reader, "decoding tpc_begin")?; 141 | Zeo::TpcBegin(txn, user.to_vec(), desc.to_vec(), ext.to_vec()) 142 | }, 143 | "storea" => { 144 | let (oid, committed, data, txn): (ByteBuf, ByteBuf, ByteBuf, u64) = 145 | decode!(&mut reader, "decoding storea")?; 146 | let oid = util::read8(&mut (&*oid)).context("storea oid")?; 147 | let committed = 148 | util::read8(&mut (&*committed)) 149 | .context("storea committed")?; 150 | Zeo::Storea(oid, committed, data.to_vec(), txn) 151 | }, 152 | "vote" => { 153 | let (txn,): (u64,) = decode!(&mut reader, "decoding vote")?; 154 | Zeo::Vote(id, txn) 155 | }, 156 | "tpc_finish" => { 157 | let (txn,): (u64,) = decode!(&mut reader, "decoding tpc_finish")?; 158 | Zeo::TpcFinish(id, txn) 159 | }, 160 | "tpc_abort" => { 161 | let (txn,): (u64,) = decode!(&mut reader, "decoding tpc_abort")?; 162 | Zeo::TpcAbort(id, txn) 163 | }, 164 | "new_oids" => Zeo::NewOids(id), 165 | "get_info" => Zeo::GetInfo(id), 166 | "register" => { 167 | let (storage, read_only): (String, bool) = 168 | decode!(&mut reader, "decoding register")?; 169 | Zeo::Register(id, storage, read_only) 170 | }, 171 | _ => return Err(anyhow!("bad method {}", method))? 172 | }) 173 | } 174 | 175 | 176 | // ====================================================================== 177 | 178 | #[cfg(test)] 179 | mod tests { 180 | 181 | use super::*; 182 | 183 | #[test] 184 | fn parsing() { 185 | let mut buf: Vec = vec![]; 186 | 187 | // Handshake, M5 188 | buf.extend_from_slice(b"\x00\x00\x00\x02M5"); 189 | // (1, 'register', '1', false) 190 | buf.extend_from_slice( 191 | b"\x00\x00\x00\x0f\x93\x01\xa8register\x92\xa11\xc2"); 192 | // (2, 'loadBefore', (b"\0\0\0\0\0\0\0\0", b"\1\1\1\1\1\1\1\1")) 193 | buf.extend_from_slice( 194 | &[0, 0, 0, 34, 147, 2, 170, 108, 111, 97, 100, 66, 101, 195 | 102, 111, 114, 101, 146, 196, 8, 0, 0, 0, 0, 0, 0, 0, 0, 196 | 196, 8, 1, 1, 1, 1, 1, 1, 1, 1]); 197 | let reader = std::io::Cursor::new(buf); 198 | 199 | let mut it = ZeoIter::new(reader); 200 | assert_eq!(&it.next_vec().unwrap(), b"M5"); 201 | match it.next().unwrap() { 202 | Zeo::Register(1, storage, false) => { 203 | assert_eq!(&storage, "1"); 204 | }, 205 | _ => panic!("bad match") 206 | } 207 | match it.next().unwrap() { 208 | Zeo::LoadBefore(2, oid, tid) => { 209 | assert_eq!(oid, [0u8; 8]); 210 | assert_eq!(tid, [1u8; 8]); 211 | }, 212 | _ => panic!("bad match") 213 | } 214 | } 215 | 216 | #[test] 217 | fn test_size_vec() { 218 | assert_eq!(size_vec(vec![1, 2, 3]), vec![0, 0, 0, 3, 1, 2, 3]); 219 | } 220 | 221 | #[test] 222 | fn test_sencode() { 223 | let v = sencode!((1u64, "R", 42)).unwrap(); 224 | assert_eq!(v, vec![0, 0, 0, 5, 147, 1, 161, 82, 42]); 225 | } 226 | 227 | } 228 | -------------------------------------------------------------------------------- /src/msgmacros.rs: -------------------------------------------------------------------------------- 1 | pub use serde::{Deserialize, Serialize}; 2 | 3 | 4 | #[macro_export] 5 | macro_rules! decode { 6 | ($data: expr, $doing: expr) => ( 7 | { 8 | let data = $data; 9 | let mut deserializer = rmp_serde::Deserializer::new(data); 10 | Deserialize::deserialize(&mut deserializer).context($doing) 11 | } 12 | ) 13 | } 14 | 15 | 16 | #[macro_export] 17 | macro_rules! sencode { 18 | ($data: expr) => ( 19 | { 20 | let mut buf: Vec = vec![]; 21 | { 22 | let mut encoder = rmp_serde::Serializer::new(&mut buf); 23 | ($data).serialize(&mut encoder).context("encode") 24 | }.and(Ok(crate::msg::size_vec(buf))) 25 | } 26 | ) 27 | } 28 | 29 | 30 | #[macro_export] 31 | macro_rules! message { 32 | ($id: expr, $method: expr, $data: expr) => ( 33 | sencode!(($id, $method, ($data)))? 34 | ) 35 | } 36 | 37 | #[macro_export] 38 | macro_rules! response { 39 | ($id: expr, $data: expr) => ( 40 | message!($id, "R", ($data)) 41 | ) 42 | } 43 | 44 | #[macro_export] 45 | macro_rules! error_response { 46 | ($id: expr, $data: expr) => ( 47 | message!($id, "E", ($data)) 48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /src/pool.rs: -------------------------------------------------------------------------------- 1 | 2 | pub trait FileFactory { 3 | fn new(&self) -> std::io::Result; 4 | } 5 | 6 | #[derive(Debug)] 7 | pub struct ReadFileFactory { 8 | pub path: String, 9 | } 10 | 11 | impl FileFactory for ReadFileFactory { 12 | fn new(&self) -> std::io::Result { 13 | std::fs::File::open(&self.path) 14 | } 15 | } 16 | 17 | #[derive(Debug)] 18 | pub struct TmpFileFactory { 19 | base: String, 20 | } 21 | 22 | impl TmpFileFactory { 23 | pub fn base(base: String) -> std::io::Result { 24 | { 25 | if ! std::path::Path::new(&base).exists() { 26 | std::fs::create_dir(&base)?; 27 | } 28 | } 29 | Ok(TmpFileFactory { base: base }) 30 | } 31 | } 32 | 33 | impl FileFactory for TmpFileFactory { 34 | fn new(&self) -> std::io::Result { 35 | tempfile::tempfile_in(&self.base) 36 | } 37 | } 38 | 39 | pub type TmpFilePointer<'store> = PooledFilePointer<'store, TmpFileFactory>; 40 | 41 | #[derive(Debug)] 42 | pub struct FilePool { 43 | capacity: usize, // Doesn't change 44 | files: std::sync::Mutex>, 45 | factory: F, // Doesn't change 46 | } 47 | 48 | impl FilePool { 49 | pub fn new(factory: F, capacity: usize) -> FilePool { 50 | FilePool { capacity: capacity, factory: factory, 51 | files: std::sync::Mutex::new(vec![]) } 52 | } 53 | 54 | pub fn get<'pool>(&'pool self) -> std::io::Result> { 55 | let mut files = self.files.lock().unwrap(); 56 | let file = match files.pop() { 57 | Some(filerc) => filerc, 58 | None => self.factory.new()?, 59 | }; 60 | Ok(PooledFilePointer {file: file, pool: self}) 61 | } 62 | 63 | pub fn put(&self, filerc: std::fs::File) { 64 | let mut files = self.files.lock().unwrap(); 65 | if files.len() < self.capacity { 66 | files.push(filerc); 67 | } 68 | } 69 | 70 | pub fn len(&self) -> usize { 71 | self.files.lock().unwrap().len() 72 | } 73 | } 74 | 75 | unsafe impl std::marker::Sync for FilePool {} 76 | unsafe impl std::marker::Send for FilePool {} 77 | 78 | #[derive(Debug)] 79 | pub struct PooledFilePointer<'pool, F: FileFactory + 'pool> { 80 | file: std::fs::File, 81 | pool: &'pool FilePool, 82 | } 83 | 84 | impl<'pool, F: FileFactory + 'pool> std::ops::Deref for PooledFilePointer<'pool, F> { 85 | type Target = std::fs::File; 86 | 87 | fn deref<'fptr>(&'fptr self) -> &'fptr std::fs::File { 88 | &self.file 89 | } 90 | } 91 | 92 | impl<'pool, F: FileFactory + 'pool> Drop for PooledFilePointer<'pool, F> { 93 | fn drop(&mut self) { 94 | self.pool.put(self.file.try_clone().expect(r#"Cloning file"#)); 95 | } 96 | } 97 | 98 | // ====================================================================== 99 | 100 | #[cfg(test)] 101 | mod tests { 102 | 103 | use super::*; 104 | use std::io::prelude::*; 105 | use std::sync; 106 | use std::thread; 107 | 108 | use crate::util; 109 | 110 | #[test] 111 | fn works() { 112 | let tmp_dir = util::test::dir(); 113 | let sample = b"data"; 114 | let path = String::from( 115 | tmp_dir.path().join("data").to_str().unwrap()); 116 | { std::fs::File::create(&path).unwrap().write_all(sample).unwrap(); } 117 | 118 | let pool = sync::Arc::new( 119 | FilePool::new(ReadFileFactory { path: path }, 2)); 120 | let (t, r) = sync::mpsc::channel(); 121 | 122 | let count = 8; 123 | 124 | for i in 0 .. count { 125 | let tt = t.clone(); 126 | let tpool = pool.clone(); 127 | thread::spawn(move || { 128 | let p = tpool.get().unwrap(); 129 | let mut file = p.try_clone().unwrap(); 130 | let mut buf = [0u8; 4]; 131 | file.seek(std::io::SeekFrom::Start(0)).unwrap(); 132 | file.read_exact(&mut buf).unwrap(); 133 | tt.send(buf); 134 | }); 135 | } 136 | 137 | for i in 0 .. count { 138 | assert_eq!(&r.recv().unwrap(), sample); 139 | } 140 | 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/reader.rs: -------------------------------------------------------------------------------- 1 | // Read side of server. 2 | 3 | use anyhow::{anyhow, Context, Result}; 4 | 5 | use crate::storage; 6 | use crate::writer; 7 | use crate::msg; 8 | use crate::msgmacros::*; 9 | 10 | macro_rules! respond { 11 | ($sender: expr, $id: expr, $data: expr) => ( 12 | $sender.send(msg::Zeo::Raw(response!($id, $data))).context("send response")? 13 | ) 14 | } 15 | 16 | macro_rules! error { 17 | ($sender: expr, $id: expr, $data: expr) => ( 18 | $sender 19 | .send(msg::Zeo::Raw(error_response!($id, $data))) 20 | .context("send error response")? 21 | ) 22 | } 23 | 24 | pub fn reader( 25 | fs: std::sync::Arc>, 26 | reader: R, 27 | sender: std::sync::mpsc::Sender) 28 | -> Result<()> { 29 | 30 | let mut it = msg::ZeoIter::new(reader); 31 | 32 | // handshake 33 | if it.next_vec()? != b"M5".to_vec() { 34 | return Err(anyhow!("Bad handshake"))? 35 | } 36 | 37 | // register(storage_id, read_only) 38 | loop { 39 | match it.next()? { 40 | msg::Zeo::Register(id, storage, read_only) => { 41 | if &storage != "1" { 42 | error!(sender, id, 43 | ("builtins.ValueError", ("Invalid storage",))) 44 | } 45 | respond!(sender, id, msg::bytes(&fs.last_transaction())); 46 | break; // onward 47 | }, 48 | msg::Zeo::End => { 49 | sender.send(msg::Zeo::End); 50 | return Ok(()) 51 | }, 52 | _ => return Err(anyhow!("bad method"))? 53 | } 54 | } 55 | 56 | // Main loop. We spend most of our time here. 57 | loop { 58 | let message = it.next()?; 59 | match message { 60 | msg::Zeo::LoadBefore(id, oid, before) => { 61 | use storage::LoadBeforeResult::*; 62 | match fs.load_before(&oid, &before)? { 63 | Loaded(data, tid, Some(end)) => { 64 | respond!( 65 | sender, id, 66 | (msg::bytes(&data), msg::bytes(&tid), msg::bytes(&end))); 67 | }, 68 | Loaded(data, tid, None) => { 69 | respond!( 70 | sender, id, 71 | (msg::bytes(&data), msg::bytes(&tid), msg::NIL)); 72 | }, 73 | NoneBefore => { 74 | respond!(sender, id, msg::NIL); 75 | }, 76 | PosKeyError => { 77 | error!(sender, id, 78 | ("ZODB.POSException.POSKeyError", 79 | (msg::bytes(&oid),))); 80 | }, 81 | } 82 | }, 83 | msg::Zeo::Ping(id) => { 84 | respond!(sender, id, msg::NIL); 85 | }, 86 | msg::Zeo::NewOids(id) => { 87 | let oids = fs.new_oids(); 88 | let oids: Vec = 89 | oids.iter().map(| oid | msg::bytes(oid)).collect(); 90 | respond!(sender, id, oids) 91 | }, 92 | msg::Zeo::GetInfo(id) => { // TODO, don't punt :) 93 | respond!(sender, id, std::collections::BTreeMap::::new()) 94 | }, 95 | msg::Zeo::TpcBegin(_, _, _, _) | msg::Zeo::Storea(_, _, _, _) | 96 | msg::Zeo::Vote(_, _) | msg::Zeo::TpcFinish(_, _) | msg::Zeo::TpcAbort(_, _) 97 | => 98 | sender 99 | .send(message) 100 | .context("send error")?, // Forward these 101 | msg::Zeo::End => { 102 | sender.send(msg::Zeo::End); 103 | return Ok(()) 104 | }, 105 | _ => return Err(anyhow!("bad method")) 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/records.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | use std::io::prelude::*; 3 | 4 | use byteorder::{ByteOrder, BigEndian, ReadBytesExt, WriteBytesExt}; 5 | 6 | use crate::index; 7 | use crate::util; 8 | 9 | pub static HEADER_MARKER: &'static [u8] = b"fs2 "; 10 | 11 | pub struct FileHeader { 12 | alignment: u64, 13 | previous: String, 14 | } 15 | pub const HEADER_SIZE: u64 = 4096; 16 | 17 | impl FileHeader { 18 | 19 | pub fn new() -> FileHeader { 20 | FileHeader { alignment: 1 << 32, previous: String::new() } 21 | } 22 | 23 | pub fn read(mut reader: &mut T) -> std::io::Result 24 | where T: std::io::Read + std::io::Seek 25 | { 26 | util::check_magic(&mut reader, HEADER_MARKER); 27 | util::io_assert(reader.read_u64::()? == 4096, 28 | "Bad header length")?; 29 | let alignment = reader.read_u64::()?; 30 | let h = match String::from_utf8(util::read_sized16(&mut reader)?) { 31 | Ok(previous) => 32 | FileHeader { alignment: alignment, previous: previous }, 33 | _ => return Err(util::io_error("Bad previous utf8")), 34 | }; 35 | util::io_assert(reader.seek(std::io::SeekFrom::Start(4088))? == 4088, 36 | "Seek failed")?; 37 | util::io_assert(reader.read_u64::()? == 4096, 38 | "Bad header extra length")?; 39 | Ok(h) 40 | } 41 | 42 | pub fn write(&self, writer: &mut T) -> std::io::Result<()> 43 | where T: std::io::Write + std::io::Seek 44 | { 45 | writer.write_all(&HEADER_MARKER)?; 46 | writer.write_u64::(4096)?; 47 | writer.write_u64::(self.alignment)?; 48 | writer.write_u16::(self.previous.len() as u16)?; 49 | if self.previous.len() > 0 { 50 | writer.write_all(&self.previous.clone().into_bytes())?; 51 | } 52 | util::io_assert( 53 | writer.seek(std::io::SeekFrom::Start(4088))? == 4088, 54 | "seek failed" 55 | )?; 56 | writer.write_u64::(4096)?; 57 | Ok(()) 58 | } 59 | } 60 | 61 | #[derive(PartialEq, Debug)] 62 | pub struct TransactionHeader { 63 | pub length: u64, 64 | pub id: util::Tid, 65 | pub ndata: u32, 66 | pub luser: u16, 67 | pub ldesc: u16, 68 | pub lext: u32, 69 | } 70 | pub const TRANSACTION_HEADER_LENGTH: u64 = 28; 71 | 72 | impl TransactionHeader { 73 | 74 | fn new(tid: util::Tid) -> TransactionHeader { 75 | TransactionHeader { 76 | length: 0, id: tid, luser: 0, ldesc: 0, lext: 0, ndata: 0 } 77 | } 78 | 79 | pub fn read(mut reader: &mut dyn std::io::Read) 80 | -> std::io::Result { 81 | let length = reader.read_u64::()?; 82 | let mut h = TransactionHeader::new(util::read8(&mut reader)?); 83 | h.length = length; 84 | h.ndata = reader.read_u32::()?; 85 | h.luser = reader.read_u16::()?; 86 | h.ldesc = reader.read_u16::()?; 87 | h.lext = reader.read_u32::()?; 88 | Ok(h) 89 | } 90 | 91 | pub fn update_index(&self, mut reader: &mut T, index: &mut index::Index, 92 | mut last_oid: util::Oid) 93 | -> std::io::Result 94 | where T: std::io::Read + std::io::Seek { 95 | let mut pos = 96 | reader.seek( 97 | std::io::SeekFrom::Current( 98 | self.luser as i64 + self.ldesc as i64 + self.lext as i64))?; 99 | 100 | for i in 0 .. self.ndata { 101 | let ldata = reader.read_u32::()?; 102 | let oid = util::read8(&mut reader)?; 103 | index.insert(oid, pos); 104 | if oid > last_oid { 105 | last_oid = oid; 106 | } 107 | pos += DATA_HEADER_SIZE + ldata as u64; 108 | if i + 1 < self.ndata { 109 | util::seek(&mut reader, pos)?; 110 | } 111 | } 112 | 113 | Ok(last_oid) 114 | } 115 | 116 | } 117 | 118 | #[derive(PartialEq, Debug)] 119 | pub struct DataHeader { 120 | pub length: u32, 121 | pub id: util::Oid, 122 | pub tid: util::Tid, 123 | pub previous: u64, 124 | pub offset: u64, 125 | } 126 | pub const DATA_HEADER_SIZE: u64 = 36; 127 | pub const DATA_TID_OFFSET: u64 = 12; 128 | pub const DATA_PREVIOUS_OFFSET: u64 = 20; 129 | 130 | impl DataHeader { 131 | 132 | fn new(tid: util::Tid) -> TransactionHeader { 133 | TransactionHeader { 134 | length: 0, id: tid, luser: 0, ldesc: 0, lext: 0, ndata: 0 } 135 | } 136 | 137 | pub fn read(reader: &mut dyn std::io::Read) -> std::io::Result { 138 | // assume reader is unbuffered 139 | let mut buf = [0u8; DATA_HEADER_SIZE as usize]; 140 | reader.read_exact(&mut buf)?; 141 | Ok(DataHeader { 142 | length: BigEndian::read_u32(&buf[0..4]), 143 | id: util::read8(&mut &buf[4..])?, 144 | tid: util::read8(&mut &buf[12..])?, 145 | previous: BigEndian::read_u64(&buf[20..]), 146 | offset: BigEndian::read_u64(&buf[28..]), 147 | }) 148 | } 149 | } 150 | 151 | 152 | 153 | 154 | // ====================================================================== 155 | 156 | #[cfg(test)] 157 | mod tests { 158 | 159 | pub use super::*; 160 | 161 | fn file_header_sample(previous: &[u8]) -> Vec { 162 | let mut sample = vec![0u8; 0]; 163 | sample.extend_from_slice(&HEADER_MARKER); 164 | sample.extend_from_slice(&[0, 0, 0, 0, 0, 0, 16, 0]); // 4096 165 | sample.extend_from_slice(&[0, 0, 0, 0, 64, 0, 0, 0]); // 1<<30 166 | sample.extend_from_slice(&vec![0u8, previous.len() as u8][..]); 167 | sample.extend_from_slice(&previous); 168 | sample.extend_from_slice(&vec![0; 4066 - previous.len()]); 169 | sample.extend_from_slice(&[0, 0, 0, 0, 0, 0, 16, 0]); // 4096 170 | sample 171 | } 172 | 173 | #[test] 174 | fn read_file_header() { 175 | let mut reader = std::io::Cursor::new(file_header_sample(b"")); 176 | let h = FileHeader::read(&mut reader).unwrap(); 177 | assert_eq!(&h.previous, ""); 178 | assert_eq!(h.alignment, 1<<30); 179 | 180 | let mut reader = std::io::Cursor::new(file_header_sample(b"previous")); 181 | let h = FileHeader::read(&mut reader).unwrap(); 182 | assert_eq!(h.previous, "previous"); 183 | assert_eq!(h.alignment, 1<<30); 184 | } 185 | 186 | #[test] 187 | fn write_file_header() { 188 | 189 | let mut writer = std::io::Cursor::new(vec![0u8; 0]); 190 | let h = FileHeader { 191 | previous: String::new(), 192 | alignment: 1<<30, 193 | }; 194 | h.write(&mut writer).unwrap(); 195 | assert_eq!(writer.into_inner(), file_header_sample(b"")); 196 | 197 | let mut writer = std::io::Cursor::new(vec![0u8; 0]); 198 | let h = FileHeader { 199 | previous: String::from("previous"), 200 | alignment: 1<<30, 201 | }; 202 | h.write(&mut writer).unwrap(); 203 | assert_eq!(writer.into_inner(), file_header_sample(b"previous")); 204 | } 205 | 206 | #[test] 207 | fn read_transaction_header() { 208 | // Note that the transaction-header read method is called 209 | // after reading the record marker. 210 | 211 | let mut cursor = std::io::Cursor::new(Vec::new()); 212 | 213 | // Write out some sample data: 214 | util::write_u64(&mut cursor, 9999).unwrap(); 215 | cursor.write_all(&util::p64(1234567890)).unwrap(); 216 | util::write_u32(&mut cursor, 2).unwrap(); 217 | util::write_u16(&mut cursor, 11).unwrap(); 218 | util::write_u16(&mut cursor, 22).unwrap(); 219 | util::write_u32(&mut cursor, 33).unwrap(); 220 | util::seek(&mut cursor, 0).unwrap(); 221 | 222 | let h = TransactionHeader::read(&mut cursor).unwrap(); 223 | assert_eq!( 224 | h, 225 | TransactionHeader { 226 | length: 9999, id: util::p64(1234567890), ndata: 2, 227 | luser: 11, ldesc: 22, lext: 33, 228 | }); 229 | } 230 | 231 | } 232 | -------------------------------------------------------------------------------- /src/server.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | Server architecture 3 | =================== 4 | 5 | There is a main thread that listens for connections. 6 | 7 | For each client, there are two threads, and a channel (and a 8 | TcpStream, of course): 9 | 10 | reader 11 | The reader thread reads incoming requests. 12 | 13 | For read requests, it performs requests and then sends results over 14 | the channel to the writer to send the results back to the client. 15 | 16 | Write requests are forwarded to the writer (below) over the channel. 17 | 18 | writer 19 | The writer writes data back to the client. It also manages transaction. 20 | 21 | It receives results from the reader over the channel and sends them 22 | to the client over the TcpStream. 23 | 24 | It also gets write requests forwarded from the reader, which it acts 25 | on itself. 26 | 27 | In the course of managing transactions, it gets locking and 28 | transaction-finish notifications over the channel. It sends 29 | responses to non-asyncchronous write requests (vote, tcp_finish, 30 | tcp_abort) to the client over the TcpStream. Vote and finish 31 | responses are delayed. 32 | 33 | Only readers read from the TcpStreams and only writers write to 34 | TcpStreams. Synchronization of readers and writers happens through 35 | their channels. 36 | 37 | Transaction locking happens in the storage using object-level 38 | locks. When clients successfully vote, their data is written to the 39 | database but marked as provisional. When the client sends a 40 | tpc_finish request, the transaction records are marked as final, 41 | however, notifications may be delayed. 42 | 43 | Transaction ids are assigned when votes succeed. This means that 44 | there's a queue, in transaction id order, of voted transactions 45 | waiting to be finished. When a transaction is finished, it's marked 46 | as such in the queue. When a finished transaction reaches the front 47 | of the queue, the database index is updated and notifications are sent 48 | to clients, including the committing client. 49 | -------------------------------------------------------------------------------- /src/storage.rs: -------------------------------------------------------------------------------- 1 | /// filestorage2 2 | 3 | use std::io::prelude::*; 4 | 5 | use anyhow::{Context, Result}; 6 | use byteorder::{ByteOrder, BigEndian, ReadBytesExt}; 7 | 8 | use crate::errors; 9 | use crate::index; 10 | use crate::lock; 11 | use crate::pool; 12 | use crate::records; 13 | use crate::tid; 14 | use crate::transaction; 15 | 16 | use crate::util; 17 | 18 | const INDEX_SUFFIX: &'static str = ".index"; 19 | const TRANSACTION_MARKER: &'static [u8] = b"TTTT"; 20 | 21 | #[derive(Debug)] 22 | pub enum LoadBeforeResult { 23 | Loaded(util::Bytes, util::Tid, Option), 24 | NoneBefore, 25 | PosKeyError, 26 | } 27 | 28 | #[derive(Debug, PartialEq)] 29 | pub struct Conflict { 30 | pub oid: util::Oid, 31 | pub serial: util::Tid, 32 | pub committed: util::Tid, 33 | pub data: util::Bytes, 34 | } 35 | 36 | pub struct FileStorage { 37 | path: String, 38 | voted: std::sync::Mutex>>, 39 | file: std::sync::Mutex, 40 | index: std::sync::Mutex, 41 | readers: pool::FilePool, 42 | tmps: pool::FilePool, 43 | last_tid: std::sync::Mutex, 44 | committed_tid: std::sync::Mutex, 45 | locker: std::sync::Mutex, 46 | clients: std::sync::Mutex>, 47 | last_oid: std::sync::Mutex, 48 | // TODO header: FileHeader, 49 | } 50 | 51 | pub struct Voted { 52 | id: util::Tid, 53 | pos: u64, 54 | tid: util::Tid, 55 | length: u64, 56 | index: index::Index, 57 | finished: Option, 58 | } 59 | 60 | pub trait Client: PartialEq + Send + Clone + std::fmt::Debug { 61 | fn finished(&self, tid: &util::Tid, len: u64, size: u64) -> Result<()>; 62 | fn invalidate(&self, tid: &util::Tid, oids: &Vec) -> Result<()>; 63 | fn close(&self); 64 | } 65 | 66 | impl FileStorage { 67 | 68 | fn new(path: String, file: std::fs::File, index: index::Index, 69 | last_tid: util::Tid, last_oid: util::Oid) 70 | -> std::io::Result> { 71 | let last_oid = BigEndian::read_u64(&last_oid); 72 | Ok(FileStorage { 73 | readers: pool::FilePool::new( 74 | pool::ReadFileFactory { path: path.clone() }, 9), 75 | tmps: pool::FilePool::new( 76 | pool::TmpFileFactory::base(path.clone() + ".tmp")?, 77 | 22), 78 | path: path, 79 | file: std::sync::Mutex::new(file), 80 | index: std::sync::Mutex::new(index), 81 | committed_tid: std::sync::Mutex::new(last_tid), 82 | last_tid: std::sync::Mutex::new(last_tid), 83 | locker: std::sync::Mutex::new(lock::LockManager::new()), 84 | voted: std::sync::Mutex::new(std::collections::VecDeque::new()), 85 | clients: std::sync::Mutex::new(Vec::new()), 86 | last_oid: std::sync::Mutex::new(last_oid), 87 | }) 88 | } 89 | 90 | pub fn open(path: String) -> std::io::Result> { 91 | let mut file = 92 | std::fs::OpenOptions::new() 93 | .read(true).write(true).create(true) 94 | .open(&path)?; 95 | let size = file.metadata()?.len(); 96 | if size == 0 { 97 | records::FileHeader::new().write(&mut file)?; 98 | FileStorage::new(path, file, index::Index::new(), util::Z64, util::Z64) 99 | } 100 | else { 101 | records::FileHeader::read(&mut file); // TODO use header info 102 | let (index, last_tid, last_oid) = FileStorage::::load_index( 103 | &(path.clone() + INDEX_SUFFIX), &mut file, size)?; 104 | FileStorage::new(path, file, index, last_tid, last_oid) 105 | } 106 | } 107 | 108 | pub fn add_client(&self, client: C) { 109 | self.clients.lock().unwrap().push(client); 110 | } 111 | 112 | pub fn remove_client(&self, client: C) { 113 | let mut clients = self.clients.lock().unwrap(); 114 | clients.retain(| c | c != &client); 115 | } 116 | 117 | pub fn client_count(&self) -> usize { 118 | self.clients.lock().unwrap().len() 119 | } 120 | 121 | fn load_index(path: &str, mut file: &std::fs::File, size: u64) 122 | -> std::io::Result<(index::Index, util::Tid, util::Oid)> { 123 | 124 | let (mut index, segment_size, mut end) = 125 | if std::path::Path::new(&path).exists() { 126 | let (index, segment_size, start, end) = 127 | index::load_index(path)?; 128 | util::io_assert(size >= segment_size, "Index bad segment length")?; 129 | file.seek(std::io::SeekFrom::Start(records::HEADER_SIZE + 12))?; 130 | util::io_assert(util::read8(&mut file)? == start, "Index bad start")?; 131 | file.seek(std::io::SeekFrom::Start(segment_size - 8))?; 132 | util::io_assert(util::read8(&mut file)? == end, "Index bad end")?; 133 | (index, segment_size, end) 134 | } 135 | else { 136 | (index::Index::new(), records::HEADER_SIZE, util::Z64) 137 | }; 138 | 139 | let mut last_oid = util::Z64; 140 | if segment_size < size { 141 | // Read newer records into index 142 | let mut reader = std::io::BufReader::new(file.try_clone()?); 143 | let mut pos = segment_size; 144 | util::seek(&mut reader, pos)?; 145 | while pos < size { 146 | let marker = util::read4(&mut reader)?; 147 | let length = match &marker { 148 | m if m == TRANSACTION_MARKER => { 149 | let header = 150 | records::TransactionHeader::read(&mut reader)?; 151 | last_oid = header.update_index( 152 | &mut reader, &mut index, last_oid)?; 153 | assert!(header.id > end); 154 | end = header.id; 155 | header.length 156 | }, 157 | m if m == transaction::PADDING_MARKER => { 158 | reader.read_u64::()? 159 | }, 160 | _ => { 161 | util::io_assert( 162 | false, &format!("Bad record marker {:?}", &marker))?; 163 | 0 164 | } 165 | }; 166 | pos += length; 167 | util::seek(&mut reader, pos - 8)?; 168 | assert_eq!(util::read_u64(&mut reader)?, length); 169 | } 170 | } 171 | Ok((index, end, last_oid)) 172 | } 173 | 174 | fn new_tid(&self) -> util::Tid { 175 | let mut last_tid = self.last_tid.lock().unwrap(); 176 | *last_tid = tid::later_than(tid::now_tid(), *last_tid); 177 | *last_tid 178 | } 179 | 180 | fn lookup_pos(&self, oid: &util::Oid) -> Option { 181 | let index = self.index.lock().unwrap(); 182 | index.get(oid).map(| pos | *pos) 183 | } 184 | 185 | pub fn load_before(&self, oid: &util::Oid, tid: &util::Tid) 186 | -> Result { 187 | match self.lookup_pos(oid) { 188 | Some(pos) => { 189 | let p = self.readers.get().context("getting reader")?; 190 | let mut file = p.try_clone()?; 191 | file.seek(std::io::SeekFrom::Start(pos)) 192 | .context("seeking to object record")?; 193 | let mut header = 194 | records::DataHeader::read(&mut &file) 195 | .context("Reading object header")?; 196 | let mut next: Option = None; 197 | while &header.tid >= tid { 198 | if header.previous == 0 { 199 | return Ok(LoadBeforeResult::NoneBefore); 200 | } 201 | next = Some(header.tid); 202 | file.seek(std::io::SeekFrom::Start(header.previous)) 203 | .context("seeking to previous")?; 204 | header = 205 | records::DataHeader::read(&mut &file) 206 | .context("reading previous header")?; 207 | } 208 | Ok(LoadBeforeResult::Loaded( 209 | util::read_sized(&mut &file, header.length as usize) 210 | .context("Reading object data")?, 211 | header.tid, next)) 212 | }, 213 | None => Ok(LoadBeforeResult::PosKeyError), 214 | } 215 | } 216 | 217 | pub fn lock(&self, 218 | transaction: &transaction::Transaction, 219 | locked: Box) 220 | -> Result<()> { 221 | let (tid, oids) = transaction.lock_data()?; 222 | let mut locker = self.locker.lock().unwrap(); 223 | locker.lock(tid, oids, locked); 224 | Ok(()) 225 | } 226 | 227 | pub fn new_oids(&self) -> Vec { 228 | let mut last_oid = self.last_oid.lock().unwrap(); 229 | let result: Vec = 230 | (*last_oid + 1 .. *last_oid + 101).map(| oid | util::p64(oid)).collect(); 231 | *last_oid += 100; 232 | result 233 | } 234 | 235 | pub fn tpc_begin(&self, user: &[u8], desc: &[u8], ext: &[u8]) 236 | -> std::io::Result { 237 | Ok(transaction::Transaction::begin( 238 | self.tmps.get()?, 239 | self.new_tid(), user, desc, ext)?) 240 | } 241 | 242 | pub fn stage(&self, trans: &mut transaction::Transaction) 243 | -> Result> { 244 | 245 | // Check for conflicts 246 | let oid_serials = { 247 | let mut oid_serials: Vec<(util::Oid, util::Tid)> = vec![]; 248 | for r in trans.serials().context("transaction serials")? { 249 | oid_serials.push(r.context("transaction serial")?); 250 | }; 251 | oid_serials 252 | }; 253 | let oid_serial_pos = { 254 | let index = self.index.lock().unwrap(); 255 | oid_serials.iter().map( 256 | | t | { 257 | let (oid, serial) = *t; 258 | (oid, serial, index.get(&oid).map(| r | r.clone())) 259 | }) 260 | .collect::)>>() 261 | }; 262 | let mut conflicts: Vec = vec![]; 263 | let p = self.readers.get().context("getting reader")?; 264 | let mut file = p.try_clone()?; 265 | for (oid, serial, posop) in oid_serial_pos { 266 | match posop { 267 | Some(pos) => { 268 | file.seek(std::io::SeekFrom::Start(pos+12)) 269 | .context("Seeking to serial")?; 270 | let committed = 271 | util::read8(&mut file).context("Reading serial")?; 272 | if committed != serial { 273 | let data = trans.get_data(&oid)?; 274 | conflicts.push( 275 | Conflict { oid: oid, data: data, 276 | serial: serial, committed: committed } 277 | ); 278 | } 279 | trans.set_previous(&oid, pos)?; 280 | }, 281 | None => { 282 | if serial != util::Z64 { 283 | return Err(errors::POSError::Key(oid))?; 284 | } 285 | } 286 | } 287 | } 288 | 289 | if conflicts.len() == 0 { 290 | trans.pack().context("trans pack")?; 291 | let mut voted = self.voted.lock().unwrap(); 292 | let mut file = self.file.lock().unwrap(); 293 | let tid = self.new_tid(); 294 | let pos = file.seek(std::io::SeekFrom::End(0)).context("seek end")?; 295 | let (index, length) = 296 | trans.stage(tid, &mut file).context("trans stage")?; 297 | voted.push_back( 298 | Voted { id: trans.id, pos: pos, tid: tid, index: index, 299 | finished: None, length: length }); 300 | } 301 | else { 302 | trans.unlocked()?; 303 | self.locker.lock().unwrap().release(&trans.id); 304 | } 305 | 306 | Ok(conflicts) 307 | } 308 | 309 | pub fn tpc_finish(&self, id: &util::Tid, finished: C) -> Result<()> { 310 | let mut voted = self.voted.lock().unwrap(); 311 | 312 | for v in voted.iter_mut() { 313 | if v.id == *id { 314 | v.finished = Some(finished); 315 | 316 | // Update the transaction maker right away, so if we 317 | // restart, the transaction will be there. We don't 318 | // update the index and notify clients until earlier 319 | // voted transactions have finished. 320 | let mut file = self.file.lock().unwrap(); 321 | file.seek(std::io::SeekFrom::Start(v.pos)) 322 | .context("seeking tpc_finish")?; 323 | file.write_all(TRANSACTION_MARKER) 324 | .context("writing trans marker tpc_finish")?; 325 | file.sync_all().context("fsync")?; 326 | break; 327 | } 328 | } 329 | self.handle_finished_at_voted_head(voted); 330 | Ok(()) 331 | } 332 | 333 | 334 | fn handle_finished_at_voted_head( 335 | &self, 336 | mut voted: std::sync::MutexGuard>>) { 337 | 338 | while voted.len() > 0 { 339 | { 340 | let ref mut v = voted.front().unwrap(); 341 | if let Some(ref finished) = v.finished { 342 | let len = { 343 | let mut index = self.index.lock().unwrap(); 344 | for (k, pos) in v.index.iter() { 345 | index.insert(k.clone(), *pos + v.pos); 346 | }; 347 | index.len() as u64 348 | }; 349 | 350 | let oids: Vec = v.index.keys() 351 | .map(| oid | oid.clone()) 352 | .collect(); 353 | *self.committed_tid.lock().unwrap() = v.tid; 354 | let mut clients = self.clients.lock().unwrap(); 355 | let mut clients_to_remove: Vec = vec![]; 356 | 357 | for client in clients.iter() { 358 | if client != finished { 359 | if client.invalidate(&v.tid, &oids).is_err() { 360 | clients_to_remove.push((*client).clone()); 361 | } 362 | } 363 | } 364 | if finished.finished(&v.tid, len, v.pos + v.length) 365 | .is_err() { 366 | clients_to_remove.push(finished.clone()); 367 | }; 368 | clients.retain(| c | ! clients_to_remove.contains(&c)); 369 | self.locker.lock().unwrap().release(&v.id); 370 | } 371 | else { 372 | break; 373 | } 374 | } 375 | voted.pop_front(); 376 | } 377 | } 378 | 379 | 380 | pub fn tpc_abort(&self, id: &util::Tid) { 381 | let mut voted = self.voted.lock().unwrap(); 382 | let l = voted.len(); 383 | voted.retain( 384 | | v | { 385 | if &v.id == id { 386 | self.locker.lock().unwrap().release(id); 387 | false 388 | } 389 | else { 390 | true 391 | } 392 | } 393 | ); 394 | if voted.len() == l { 395 | // May still need to unlock 396 | self.locker.lock().unwrap().release(id); 397 | } 398 | self.handle_finished_at_voted_head(voted); 399 | } 400 | 401 | pub fn last_transaction(&self) -> util::Tid { 402 | self.committed_tid.lock().unwrap().clone() 403 | } 404 | } 405 | 406 | // TODO save index on drop. 407 | // impl std::ops::Drop for FileStorage { 408 | // fn drop(&mut self) { 409 | // let mut file = self.file.lock.unwrap(); 410 | // let index = self.index.lock().unwrap(); 411 | // let size = file.seek(std::io::SeekFrom::End(0)); 412 | // let 413 | // index::save_index(&self.index, &(path.clone() + INDEX_SUFFIX)) 414 | // } 415 | 416 | // } 417 | 418 | unsafe impl std::marker::Send for FileStorage {} 419 | unsafe impl std::marker::Sync for FileStorage {} 420 | 421 | pub mod testing { 422 | 423 | use super::*; 424 | 425 | pub const MAXTID: &'static util::Tid = b"\x7f\xff\xff\xff\xff\xff\xff\xff"; 426 | 427 | #[derive(Debug, PartialEq, Clone)] 428 | struct NullClient; 429 | 430 | impl Client for NullClient { 431 | fn finished(&self, tid: &util::Tid, len: u64, size: u64) -> Result<()> { 432 | Ok(()) 433 | } 434 | fn invalidate(&self, tid: &util::Tid, oids: &Vec) -> Result<()> { 435 | Ok(()) 436 | } 437 | fn close(&self) {} 438 | } 439 | 440 | pub fn make_sample(path: &String, transactions: Vec>) 441 | -> Result<()> { 442 | // Create a storage with some initial data 443 | let fs: FileStorage = 444 | FileStorage::open(path.clone()).context("open fs")?; 445 | add_data(&fs, &NullClient, transactions) 446 | } 447 | 448 | pub fn add_data(fs: &FileStorage, 449 | client: &C, 450 | transactions: Vec>) 451 | -> Result<()> { 452 | 453 | let mut index = std::collections::BTreeMap::::new(); 454 | for saves in transactions { 455 | for &(oid, v) in saves.iter() { 456 | if let LoadBeforeResult::Loaded(_, tid, _) = 457 | fs.load_before(&oid, MAXTID)? { 458 | index.insert(oid.clone(), tid); 459 | } 460 | } 461 | let mut trans = fs.tpc_begin(b"", b"", b"").context("begin")?; 462 | for &(oid, v) in saves.iter() { 463 | let serial = index.get(&oid).or(Some(&util::Z64)).unwrap().clone(); 464 | trans.save(oid, serial, v).context("sample data")?; 465 | } 466 | fs.lock(&trans, Box::new(| _ | ()))?; 467 | trans.locked()?; 468 | assert_eq!(fs.stage(&mut trans)?.len(), 0); 469 | fs.tpc_finish(&trans.id, client.clone())?; 470 | let tid = fs.last_transaction(); 471 | } 472 | Ok(()) 473 | } 474 | } 475 | -------------------------------------------------------------------------------- /src/storage.rst: -------------------------------------------------------------------------------- 1 | ========================= 2 | Filestorage2 design notes 3 | ========================= 4 | 5 | See: https://github.com/jimfulton/filestorage2 6 | 7 | .. contents:: 8 | 9 | Overview 10 | ======== 11 | 12 | This module provides an implementation of a file storage suitable for 13 | use in a byteserver. It is simplar in functionality to a `ZODB 14 | storage 15 | `_, 16 | but has a narrower scope: 17 | 18 | - Not used directly as a storage for an object database 19 | 20 | - Not pluggable, but focussed on performance. 21 | 22 | The design is influenced by experience with `ZEO 23 | `_: 24 | 25 | - Unlike ZEO, use object level locks to allow non-overlapping 26 | transactions to be active right up to the final commit. 27 | 28 | - Allow parallel reads. All reads are for data written some time in 29 | the past so there's no need to disallow reading while writing. 30 | 31 | - Pool (and truncate after use) temporary files to avoid file-creation overhead. 32 | 33 | The implementation will be incremental. The initial focus is on 34 | writing and reading data transactionally. Other features, pack (segments) and 35 | like replication will be added over time. 36 | 37 | - There will be a thread safe storage object shared (Via Arc) among 38 | multiple threades. 39 | 40 | - Read operations will use a read-file pool, so multiple threads can 41 | read at once. 42 | 43 | - When a transaction is begin, a temporary file will be fetched from a 44 | pool and managed by the transaction. It will be used to log data 45 | received during the first phase of two-phase commit. 46 | 47 | Format 48 | ====== 49 | 50 | Files 51 | ----- 52 | 53 | - Active file, with name NAME 54 | 55 | All other files have name as NAME + '.' + extension. 56 | 57 | - New active file, extension: 'new-active'. 58 | 59 | This exists only during split. 60 | 61 | - Index file, extension: 'index' 62 | 63 | - Previous files, extension: hex end tid. 64 | 65 | - Previous file indxes, extension: hex end tid + '.index'. 66 | 67 | - temporary files, extension 'tmpN', where N is a number from 0 68 | through temporary-file pool size. 69 | 70 | - lock file, extension: 'lock' 71 | 72 | Split and packing 73 | ================= 74 | 75 | Split 76 | ----- 77 | 78 | Splits active file into a previous file and a new active file. 79 | 80 | - Current active must be non-empty. 81 | 82 | - Create a new active file with temporary extension, 'new-active'. 83 | 84 | - Create a new in-memory index for new active file, adding it to the 85 | front of the index list. 86 | 87 | - In separate thread. 88 | 89 | - Rename current active file with last tid as 90 | extension, making it a new previous file. 91 | 92 | - Rename new active file removing 'new-active extension'. 93 | 94 | - Write index for new previous/old active file. 95 | 96 | - On startup, check for new-active file and finish split operation, if 97 | necessary. 98 | 99 | Packing 100 | ------- 101 | 102 | Merges 2 or more previous files. 103 | 104 | New file as previous-file-tid of first file and name of last file. 105 | 106 | Old files are renamed by adding packYYMMDDSS.SSSSSS extension, based 107 | on time when pack was completed. 108 | 109 | Note that there is no-longer a pack time per se. Merged-file records 110 | are most current within the set. 111 | 112 | Objects 113 | ------- 114 | 115 | FileStorage 116 | Active Segment 117 | file 118 | index 119 | Previous Segment * 120 | file 121 | index 122 | -------------------------------------------------------------------------------- /src/tid.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{ByteOrder, BigEndian}; 2 | 3 | const SCONV: f64 = 60.0 / (1u64 <<32) as f64; 4 | 5 | type Tid = [u8; 8]; 6 | 7 | pub fn make_tid(year: u32, month: u32, day: u32, hour: u32, minute: u32, 8 | second: f64) 9 | -> Tid { 10 | 11 | let days = ((year - 1900) * 12 + month - 1) * 31 + day - 1; 12 | let minutes = ((days * 24 + hour) * 60 + minute) as u64; 13 | let seconds = (second / SCONV) as u64; 14 | 15 | let mut tid: Tid = [0u8; 8]; 16 | BigEndian::write_u64(&mut tid, (minutes << 32) + seconds); 17 | tid 18 | } 19 | 20 | pub fn tm_tid(tm: time::Tm) -> Tid { 21 | let days = (tm.tm_year * 12 + tm.tm_mon) * 31 + tm.tm_mday - 1; 22 | let minutes = ((days * 24 + tm.tm_hour) * 60 + tm.tm_min) as u64; 23 | let seconds = (( 24 | (tm.tm_sec - tm.tm_utcoff) as f64 + 25 | (tm.tm_nsec as f64 / 1_000_000_000.0) 26 | )/ SCONV) as u64; 27 | 28 | let mut tid: Tid = [0u8; 8]; 29 | BigEndian::write_u64(&mut tid, (minutes << 32) + seconds); 30 | tid 31 | } 32 | 33 | pub fn now_tid() -> Tid { tm_tid(time::now_utc()) } 34 | 35 | pub fn next(tid: &Tid) -> Tid { 36 | let mut next = tid.clone(); 37 | let iold = BigEndian::read_u64(&mut next); 38 | BigEndian::write_u64(&mut next, iold + 1); 39 | next 40 | } 41 | 42 | pub fn later_than(new: Tid, old: Tid) -> Tid { 43 | if new > old { 44 | new 45 | } 46 | else { 47 | next(&old) 48 | } 49 | } 50 | 51 | // ====================================================================== 52 | 53 | #[cfg(test)] 54 | mod tests { 55 | 56 | use super::*; 57 | use time; 58 | 59 | #[test] 60 | fn test_make_tid() { 61 | assert_eq!(make_tid(2016, 1, 2, 3, 4, 59.99999999999), 62 | [3, 180, 48, 88, 255, 255, 255, 255]); 63 | assert_eq!(make_tid(2016, 1, 2, 3, 4, 56.789), 64 | [3, 180, 48, 88, 242, 76, 187, 82]); 65 | } 66 | 67 | #[test] 68 | fn test_tm_tid() { 69 | assert_eq!( 70 | tm_tid(time::Tm { 71 | tm_year: 116, tm_mon: 0, tm_mday: 2, 72 | tm_hour: 3, tm_min: 4, tm_sec: 59, 73 | tm_nsec: 999_999_999, 74 | tm_wday: 0, tm_yday: 0, tm_isdst: 0, tm_utcoff: 0, 75 | }), 76 | [3, 180, 48, 88, 255, 255, 255, 255]); 77 | assert_eq!(make_tid(2016, 1, 2, 3, 4, 56.789), 78 | [3, 180, 48, 88, 242, 76, 187, 82]); 79 | } 80 | 81 | #[test] 82 | fn test_later_than() { 83 | 84 | assert_eq!(later_than([3, 180, 48, 88, 255, 255, 255, 255], 85 | [3, 180, 48, 88, 242, 76, 187, 82]), 86 | [3, 180, 48, 88, 255, 255, 255, 255]); 87 | 88 | assert_eq!(later_than([3, 180, 48, 88, 242, 76, 187, 82], 89 | [3, 180, 48, 88, 255, 255, 255, 255]), 90 | [3, 180, 48, 89, 0, 0, 0, 0]); 91 | } 92 | } 93 | 94 | -------------------------------------------------------------------------------- /src/transaction.rs: -------------------------------------------------------------------------------- 1 | use std::io::prelude::*; 2 | 3 | use anyhow::{anyhow, Context, Result}; 4 | use byteorder::{ByteOrder, BigEndian, ReadBytesExt, WriteBytesExt}; 5 | 6 | use crate::util; 7 | use crate::index; 8 | use crate::pool; 9 | use crate::records; 10 | 11 | static PADDING16: [u8; 16] = [0u8; 16]; 12 | pub const PADDING_MARKER: &'static [u8] = b"PPPP"; 13 | 14 | pub struct TransactionData<'store> { 15 | filep: pool::TmpFilePointer<'store>, 16 | writer: std::io::BufWriter, 17 | length: u64, 18 | header_length: u64, 19 | needs_to_be_packed: bool, 20 | } 21 | 22 | impl<'store> TransactionData<'store> { 23 | 24 | pub fn save_tid(&mut self, tid: util::Tid, count: u32) -> std::io::Result<()> { 25 | self.writer.seek(std::io::SeekFrom::Start(12))?; 26 | self.writer.write_all(&tid)?; 27 | self.writer.write_u32::(count)?; 28 | self.writer.flush()?; 29 | let mut wpos = self.header_length; 30 | let mut file = self.filep.try_clone()?; 31 | while wpos < self.length { 32 | file.seek(std::io::SeekFrom::Start(wpos))?; 33 | let dlen = file.read_u32::()?; 34 | file.seek( 35 | std::io::SeekFrom::Start(wpos + records::DATA_TID_OFFSET))?; 36 | file.write_all(&tid)?; 37 | wpos += records::DATA_HEADER_SIZE + dlen as u64; 38 | } 39 | Ok(()) 40 | } 41 | 42 | } 43 | 44 | pub enum TransactionState<'store> { 45 | Saving(TransactionData<'store>), 46 | Transitioning, 47 | Voting(TransactionData<'store>), 48 | Voted, 49 | } 50 | 51 | pub struct Transaction<'store> { 52 | pub id: util::Tid, 53 | pub state: TransactionState<'store>, 54 | index: index::Index, 55 | } 56 | 57 | impl<'store, 't> Transaction<'store> { 58 | 59 | pub fn begin(filep: pool::PooledFilePointer<'store, pool::TmpFileFactory>, 60 | id: util::Tid, user: &[u8], desc: &[u8], ext: &[u8]) 61 | -> std::io::Result> { 62 | let mut file = filep.try_clone()?; 63 | file.seek(std::io::SeekFrom::Start(0))?; 64 | file.set_len(0)?; 65 | let mut writer = std::io::BufWriter::new(file); 66 | writer.write_all(PADDING_MARKER)?; 67 | writer.write_all(&PADDING16)?; // tlen, tid 68 | writer.write_u32::(0 as u32)?; // count 69 | writer.write_u16::(user.len() as u16)?; 70 | writer.write_u16::(desc.len() as u16)?; 71 | writer.write_u32::(ext.len() as u32)?; 72 | if user.len() > 0 { writer.write_all(user)? } 73 | if desc.len() > 0 { writer.write_all(desc)? } 74 | if ext.len() > 0 { writer.write_all(ext)? } 75 | let length = 4u64 + records::TRANSACTION_HEADER_LENGTH + 76 | user.len() as u64 + desc.len() as u64 + ext.len() as u64; 77 | Ok(Transaction { 78 | id: id, index: index::Index::new(), 79 | state: TransactionState::Saving(TransactionData { 80 | filep: filep, writer: writer, 81 | length: length, header_length: length, 82 | needs_to_be_packed: false, 83 | }), 84 | }) 85 | } 86 | 87 | pub fn save(&mut self, oid: util::Oid, serial: util::Tid, data: &[u8]) 88 | -> std::io::Result<()> { 89 | // Save data in the first phase of 2-phase commit. 90 | if let TransactionState::Saving(ref mut tdata) = self.state { 91 | tdata.writer.write_u32::(data.len() as u32)?; 92 | tdata.writer.write_all(&oid)?; 93 | // read tid now, committed later: 94 | tdata.writer.write_all(&serial)?; 95 | util::write_u64(&mut tdata.writer, 0)?; // previous 96 | util::write_u64(&mut tdata.writer, tdata.length)?; // offset 97 | if data.len() > 0 { tdata.writer.write_all(data)? } 98 | if self.index.insert(oid, tdata.length).is_some() { 99 | // There was an earlier save for this oid. We'll want to 100 | // pack the data before committing. 101 | tdata.needs_to_be_packed = true; 102 | }; 103 | tdata.length += records::DATA_HEADER_SIZE + data.len() as u64; 104 | Ok(()) 105 | } 106 | else { Err(util::io_error("Invalid trans state")) } 107 | } 108 | 109 | pub fn lock_data(&self) -> Result<(util::Tid, Vec)> { 110 | if let TransactionState::Saving(_) = self.state { 111 | let mut oids = 112 | self.index.keys().map(| r | r.clone()).collect::>(); 113 | oids.reverse(); 114 | Ok((self.id, oids)) 115 | } 116 | else { Err(anyhow!("Invalid trans state")) } 117 | } 118 | 119 | pub fn locked(&mut self) -> Result<()> 120 | { 121 | let mut state = TransactionState::Transitioning; 122 | std::mem::swap(&mut state, &mut self.state); 123 | 124 | if let TransactionState::Saving(mut data) = state { 125 | match data.writer.flush().context("trans writer flush") { 126 | Ok(_) => { 127 | self.state = TransactionState::Voting(data); 128 | Ok(()) 129 | } 130 | err => { 131 | self.state = TransactionState::Saving(data); 132 | err 133 | }, 134 | } 135 | } 136 | else { 137 | std::mem::swap(&mut state, &mut self.state); // restore 138 | Err(anyhow!("Invalid trans state")) 139 | } 140 | } 141 | 142 | pub fn unlocked(&mut self) -> Result<()> { 143 | let mut state = TransactionState::Transitioning; 144 | std::mem::swap(&mut state, &mut self.state); 145 | if let TransactionState::Voting(mut data) = state { 146 | match util::seek(&mut data.writer, data.length) { 147 | Ok(_) => { 148 | self.state = TransactionState::Saving(data); 149 | Ok(()) 150 | } 151 | err => { 152 | self.state = TransactionState::Voting(data); 153 | Err(anyhow!("seek failed")) 154 | }, 155 | } 156 | } 157 | else { 158 | std::mem::swap(&mut state, &mut self.state); // restore 159 | Err(anyhow!("Invalid trans state")) 160 | } 161 | 162 | } 163 | 164 | pub fn serials(&'t mut self) -> std::io::Result> { 165 | if let TransactionState::Voting(ref mut data) = self.state { 166 | TransactionSerialIterator::new( 167 | data.filep.try_clone()?, 168 | &self.index, data.length, data.header_length) 169 | } 170 | else { Err(util::io_error("Invalid trans state")) } 171 | } 172 | 173 | pub fn get_data(&mut self, oid: &util::Oid) -> Result { 174 | if let TransactionState::Voting(ref mut data) = self.state { 175 | let pos = 176 | self.index.get(oid).ok_or(anyhow!("trans index error"))?; 177 | let mut file = data.filep.try_clone()?; 178 | file.seek(std::io::SeekFrom::Start(*pos)) 179 | .context("trans seek")?; 180 | let dlen = 181 | file.read_u32::() 182 | .context("trans read dlen")?; 183 | let data = if dlen > 0 { 184 | file.seek( 185 | std::io::SeekFrom::Start(pos + records::DATA_HEADER_SIZE)) 186 | .context("trans seek data")?; 187 | util::read_sized(&mut file, dlen as usize) 188 | .context("trans read data")? 189 | } 190 | else { 191 | vec![0u8; 0] 192 | }; 193 | Ok(data) 194 | } 195 | else { Err(anyhow!("Invalid trans state")) } 196 | } 197 | 198 | pub fn set_previous(&mut self, oid: &util::Oid, previous: u64) -> Result<()> { 199 | if let TransactionState::Voting(ref mut data) = self.state { 200 | let pos = 201 | self.index.get(oid).ok_or(anyhow!("trans index error"))?; 202 | let mut file = data.filep.try_clone()?; 203 | file.seek( 204 | std::io::SeekFrom::Start(pos + records::DATA_PREVIOUS_OFFSET)) 205 | .context("trans seek prev")?; 206 | file.write_u64::(previous) 207 | .context("trans write previous")?; 208 | Ok(()) 209 | } 210 | else { Err(anyhow!("Invalid trans state")) } 211 | } 212 | 213 | pub fn pack(&mut self) -> std::io::Result<()> { 214 | // If necessary, pack out records that were overwritten. 215 | // Also write length into header. 216 | if let TransactionState::Voting(ref mut data) = self.state { 217 | let mut file = data.filep.try_clone()?; 218 | 219 | if data.needs_to_be_packed { 220 | let mut rpos = data.header_length; 221 | let mut wpos = data.header_length; 222 | 223 | let mut buf = [0u8; 12]; 224 | while rpos < data.length { 225 | file.seek(std::io::SeekFrom::Start(rpos))?; 226 | file.read_exact(&mut buf)?; 227 | let dlen = BigEndian::read_u32(&buf) as u64; 228 | let oid = util::read8(&mut &buf[4..])?; 229 | let oid_pos = 230 | self.index.get(&oid) 231 | .ok_or(util::io_error("trans index get"))?.clone(); 232 | if oid_pos == rpos { 233 | // We want this one 234 | if rpos != wpos { 235 | // We need to move it. 236 | let mut rest = // tid, previous, offset, data 237 | util::read_sized( 238 | &mut file, 239 | dlen as usize + 240 | records::DATA_HEADER_SIZE as usize 241 | - 12)?; 242 | // update offset: 243 | util::write_u64(&mut &mut rest[16..24], wpos); 244 | file.seek(std::io::SeekFrom::Start(wpos))?; 245 | file.write_all(&buf)?; 246 | file.write_all(&rest)?; 247 | self.index.insert(oid, wpos); 248 | wpos += dlen + records::DATA_HEADER_SIZE; 249 | } 250 | } 251 | rpos += dlen + records::DATA_HEADER_SIZE; 252 | } 253 | file.set_len(wpos)?; 254 | data.length = wpos; 255 | } 256 | 257 | // Update header w length 258 | let full_length = data.length + 8; 259 | file.seek(std::io::SeekFrom::Start(data.length))?; 260 | file.write_u64::(full_length)?; 261 | file.seek(std::io::SeekFrom::Start(4))?; 262 | file.write_u64::(full_length)?; 263 | 264 | Ok(()) 265 | } 266 | else { Err(util::io_error("Invalid trans state")) } 267 | } 268 | 269 | pub fn stage(&mut self, tid: util::Tid, mut out: &mut std::fs::File) 270 | -> std::io::Result<(index::Index, u64)> { 271 | let length = 272 | if let TransactionState::Voting(ref mut data) = self.state { 273 | // Update tids in temp file 274 | data.save_tid(tid, self.index.len() as u32)?; 275 | let mut file = data.filep.try_clone()?; 276 | file.seek(std::io::SeekFrom::Start(0))?; 277 | 278 | data.length += 8; 279 | assert_eq!(std::io::copy(&mut file, &mut out)?, data.length); 280 | 281 | // Truncate to 0 in hopes of avoiding write to disk 282 | file.set_len(0)?; 283 | data.length 284 | } 285 | else { 286 | return Err(util::io_error("Invalid trans state")) 287 | }; 288 | self.state = TransactionState::Voted; 289 | 290 | let mut index = index::Index::new(); 291 | std::mem::swap(&mut index, &mut self.index); 292 | 293 | Ok((index, length)) 294 | } 295 | } 296 | 297 | impl<'store, 't> std::fmt::Debug for Transaction<'store> { 298 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 299 | write!(f, "Transaction()") // TODO: more informative :) 300 | } 301 | } 302 | 303 | type TransactionSerialIteratorItem = std::io::Result<(util::Oid, util::Tid)>; 304 | 305 | pub struct TransactionSerialIterator<'t> { 306 | reader: std::io::BufReader, 307 | index: &'t index::Index, 308 | length: u64, 309 | pos: u64, 310 | } 311 | 312 | impl<'t> TransactionSerialIterator<'t> { 313 | 314 | fn new(mut file: std::fs::File, 315 | index: &'t index::Index, 316 | length: u64, 317 | pos: u64) -> std::io::Result { 318 | file.seek(std::io::SeekFrom::Start(pos))?; 319 | Ok(TransactionSerialIterator { 320 | reader: std::io::BufReader::new(file), 321 | index: index, length: length, pos: pos }) 322 | } 323 | 324 | fn read(&mut self) -> TransactionSerialIteratorItem { 325 | loop { 326 | let dlen = self.reader.read_u32::()?; 327 | let oid = util::read8(&mut self.reader)?; 328 | match self.index.get(&oid) { 329 | Some(&pos) => { 330 | if &pos != &self.pos { 331 | // The object was repeated and this isn't the last 332 | self.reader.seek( 333 | std::io::SeekFrom::Current(24 + dlen as i64))?; 334 | self.pos += records::DATA_HEADER_SIZE + dlen as u64; 335 | continue 336 | } 337 | }, 338 | None => { 339 | return Err(util::io_error("index fail in transaction")) 340 | } 341 | } 342 | let tid = util::read8(&mut self.reader)?; 343 | self.reader.seek(std::io::SeekFrom::Current(16 + dlen as i64))?; 344 | self.pos += records::DATA_HEADER_SIZE + dlen as u64; 345 | return Ok((oid, tid)) 346 | } 347 | } 348 | } 349 | 350 | impl<'t> std::iter::Iterator for TransactionSerialIterator<'t> { 351 | 352 | type Item = TransactionSerialIteratorItem; 353 | 354 | fn next(&mut self) -> Option { 355 | if self.pos < self.length { 356 | Some(self.read()) 357 | } 358 | else { 359 | None 360 | } 361 | } 362 | } 363 | 364 | 365 | // ====================================================================== 366 | 367 | #[cfg(test)] 368 | pub mod tests { 369 | 370 | use super::*; 371 | use crate::index; 372 | use crate::pool; 373 | use crate::records; 374 | use crate::util; 375 | 376 | #[test] 377 | fn works_w_dup() { 378 | let tmpdir = util::test::dir(); 379 | 380 | let pool = pool::FilePool::new( 381 | pool::TmpFileFactory::base( 382 | String::from( 383 | tmpdir.path().join("tmp").to_str().unwrap())).unwrap(), 384 | 22); 385 | 386 | let tempfilep = pool.get().unwrap(); 387 | let tempfile = tempfilep.try_clone().unwrap(); 388 | 389 | let mut trans = Transaction::begin( 390 | tempfilep, util::p64(1234567890), b"user", b"desc", b"{}").unwrap(); 391 | 392 | trans.save(util::p64(0), util::p64(123456789), &[1; 11]).unwrap(); 393 | trans.save(util::p64(1), util::p64(12345678), &[2; 22]).unwrap(); 394 | trans.save(util::p64(0), util::p64(123456788), &[3; 33]).unwrap(); 395 | assert_eq!(trans.lock_data().unwrap(), 396 | (util::p64(1234567890), vec![util::p64(1), util::p64(0)])); 397 | trans.locked().unwrap(); 398 | let mut serials = trans.serials().unwrap() 399 | .map(| r | r.unwrap()) 400 | .collect::>(); 401 | serials.sort(); 402 | assert_eq!(serials, 403 | vec![(util::p64(0), util::p64(123456788)), 404 | (util::p64(1), util::p64(12345678))]); 405 | assert_eq!(trans.get_data(&util::p64(0)).unwrap(), vec![3; 33]); 406 | assert_eq!(trans.get_data(&util::p64(1)).unwrap(), vec![2; 22]); 407 | trans.set_previous(&util::p64(0), 7777); 408 | 409 | trans.pack().unwrap(); 410 | 411 | let t2 = pool.get().unwrap(); 412 | let mut file = t2.try_clone().unwrap(); 413 | let (index, tsize) = trans.stage(util::p64(1234567891), &mut file).unwrap(); 414 | 415 | // Now, we'll verify the saved data. 416 | let l = file.seek(std::io::SeekFrom::End(0)).unwrap(); 417 | assert_eq!(tsize, l); 418 | util::seek(&mut file, 0).unwrap(); 419 | assert_eq!(&util::read4(&mut file).unwrap(), b"PPPP"); 420 | let th = records::TransactionHeader::read(&mut file).unwrap(); 421 | assert_eq!( 422 | th, 423 | records::TransactionHeader { 424 | length: l, id: util::p64(1234567891), ndata: 2, 425 | luser: 4, ldesc: 4, lext: 2 }); 426 | assert_eq!(&util::read4(&mut file).unwrap(), b"user"); 427 | assert_eq!(&util::read4(&mut file).unwrap(), b"desc"); 428 | assert_eq!(&util::read_sized(&mut file, 2).unwrap(), b"{}"); 429 | 430 | let dh1 = records::DataHeader::read(&mut file).unwrap(); 431 | assert_eq!( 432 | dh1, 433 | records::DataHeader { 434 | length: 22, id: util::p64(1), tid: util::p64(1234567891), 435 | previous: 0, 436 | offset: records::TRANSACTION_HEADER_LENGTH + 14, 437 | }); 438 | assert_eq!(util::read_sized(&mut file, dh1.length as usize).unwrap(), 439 | vec![2; 22]); 440 | 441 | let dh0 = records::DataHeader::read(&mut file).unwrap(); 442 | assert_eq!( 443 | dh0, 444 | records::DataHeader { 445 | length: 33, id: util::p64(0), tid: util::p64(1234567891), 446 | previous: 7777, 447 | offset: 448 | dh1.offset + records::DATA_HEADER_SIZE + dh1.length as u64, 449 | }); 450 | assert_eq!(util::read_sized(&mut file, dh0.length as usize).unwrap(), 451 | vec![3; 33]); 452 | 453 | assert_eq!(util::read_u64(&mut file).unwrap(), l); // Check redundant length 454 | 455 | assert_eq!( 456 | index, { 457 | let mut other = index::Index::new(); 458 | other.insert(util::p64(0), dh0.offset); 459 | other.insert(util::p64(1), dh1.offset); 460 | other 461 | }); 462 | } 463 | 464 | #[test] 465 | fn works_wo_dup() { 466 | let tmpdir = util::test::dir(); 467 | 468 | let pool = pool::FilePool::new( 469 | pool::TmpFileFactory::base( 470 | String::from( 471 | tmpdir.path().join("tmp").to_str().unwrap())).unwrap(), 472 | 22); 473 | 474 | let tempfilep = pool.get().unwrap(); 475 | let tempfile = tempfilep.try_clone().unwrap(); 476 | 477 | let mut trans = Transaction::begin( 478 | tempfilep, util::p64(1234567890), b"user", b"desc", b"{}").unwrap(); 479 | 480 | trans.save(util::p64(0), util::p64(123456789), &[1; 11]).unwrap(); 481 | trans.save(util::p64(1), util::p64(12345678), &[2; 22]).unwrap(); 482 | assert_eq!(trans.lock_data().unwrap(), 483 | (util::p64(1234567890), vec![util::p64(1), util::p64(0)])); 484 | trans.locked().unwrap(); 485 | let mut serials = trans.serials().unwrap() 486 | .map(| r | r.unwrap()) 487 | .collect::>(); 488 | serials.sort(); 489 | assert_eq!(serials, 490 | vec![(util::p64(0), util::p64(123456789)), 491 | (util::p64(1), util::p64(12345678))]); 492 | assert_eq!(trans.get_data(&util::p64(0)).unwrap(), vec![1; 11]); 493 | assert_eq!(trans.get_data(&util::p64(1)).unwrap(), vec![2; 22]); 494 | trans.set_previous(&util::p64(0), 7777); 495 | 496 | trans.pack().unwrap(); 497 | 498 | let t2 = pool.get().unwrap(); 499 | 500 | assert_eq!(pool.len(), 0); 501 | 502 | let mut file = t2.try_clone().unwrap(); 503 | let (index, tsize) = trans.stage(util::p64(1234567891), &mut file).unwrap(); 504 | 505 | assert_eq!(pool.len(), 1); // The transaction's tmp file ws returned. 506 | 507 | // Now, we'll verify the saved data. 508 | let l = file.seek(std::io::SeekFrom::End(0)).unwrap(); 509 | assert_eq!(tsize, l); 510 | util::seek(&mut file, 0).unwrap(); 511 | assert_eq!(&util::read4(&mut file).unwrap(), b"PPPP"); 512 | let th = records::TransactionHeader::read(&mut file).unwrap(); 513 | assert_eq!( 514 | th, 515 | records::TransactionHeader { 516 | length: l, id: util::p64(1234567891), ndata: 2, 517 | luser: 4, ldesc: 4, lext: 2 }); 518 | assert_eq!(&util::read4(&mut file).unwrap(), b"user"); 519 | assert_eq!(&util::read4(&mut file).unwrap(), b"desc"); 520 | assert_eq!(&util::read_sized(&mut file, 2).unwrap(), b"{}"); 521 | 522 | let dh0 = records::DataHeader::read(&mut file).unwrap(); 523 | assert_eq!( 524 | dh0, 525 | records::DataHeader { 526 | length: 11, id: util::p64(0), tid: util::p64(1234567891), 527 | previous: 7777, 528 | offset: records::TRANSACTION_HEADER_LENGTH + 14, 529 | }); 530 | assert_eq!(util::read_sized(&mut file, dh0.length as usize).unwrap(), 531 | vec![1; 11]); 532 | 533 | let dh1 = records::DataHeader::read(&mut file).unwrap(); 534 | assert_eq!( 535 | dh1, 536 | records::DataHeader { 537 | length: 22, id: util::p64(1), tid: util::p64(1234567891), 538 | previous: 0, 539 | offset: 540 | dh0.offset + records::DATA_HEADER_SIZE + dh0.length as u64, 541 | }); 542 | assert_eq!(util::read_sized(&mut file, dh1.length as usize).unwrap(), 543 | vec![2; 22]); 544 | 545 | assert_eq!(util::read_u64(&mut file).unwrap(), l); // Check redundant length 546 | 547 | assert_eq!( 548 | index, { 549 | let mut other = index::Index::new(); 550 | other.insert(util::p64(0), dh0.offset); 551 | other.insert(util::p64(1), dh1.offset); 552 | other 553 | }); 554 | } 555 | } 556 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{ByteOrder, BigEndian, ReadBytesExt, WriteBytesExt}; 2 | 3 | pub fn io_assert(cond: bool, message: &str) -> std::io::Result<()> { 4 | if cond { 5 | Ok(()) 6 | } else { 7 | Err(io_error(message)) 8 | } 9 | } 10 | 11 | pub type Tid = [u8; 8]; 12 | pub type Oid = [u8; 8]; 13 | pub type Bytes = Vec; 14 | 15 | pub static Z64: [u8; 8] = [0u8; 8]; 16 | pub fn p64(i: u64) -> [u8; 8] { 17 | let mut r = [0u8; 8]; 18 | BigEndian::write_u64(&mut r, i); 19 | r 20 | } 21 | 22 | pub fn io_error(message: &str) -> std::io::Error { 23 | std::io::Error::new(std::io::ErrorKind::Other, message) 24 | } 25 | 26 | pub fn check_magic( 27 | reader: &mut dyn std::io::Read, magic: &[u8]) -> std::io::Result<()> { 28 | let mut buf = [0u8; 4]; 29 | reader.read_exact(&mut buf)?; 30 | io_assert(&buf == magic, "bad magic")?; 31 | Ok(()) 32 | } 33 | 34 | pub fn read_sized(reader: &mut dyn std::io::Read, size: usize) 35 | -> std::io::Result> { 36 | if size > 0 { 37 | let mut r = vec![0u8; size]; 38 | reader.read_exact(&mut r)?; 39 | Ok(r) 40 | } 41 | else { 42 | Ok(vec![0u8; 0]) 43 | } 44 | } 45 | 46 | 47 | pub fn read_sized16(reader: &mut dyn std::io::Read) -> std::io::Result> { 48 | let size = reader.read_u16::()? as usize; 49 | read_sized(reader, size) 50 | } 51 | 52 | pub fn read1(reader: &mut dyn std::io::Read) -> std::io::Result { 53 | let mut r = [0u8]; 54 | reader.read_exact(&mut r)?; 55 | Ok(r[0]) 56 | } 57 | 58 | pub fn read4(reader: &mut dyn std::io::Read) -> std::io::Result<[u8; 4]> { 59 | let mut r = [0u8; 4]; 60 | reader.read_exact(&mut r)?; 61 | Ok::<[u8; 4], std::io::Error>(r) 62 | } 63 | 64 | pub fn read8(reader: &mut dyn std::io::Read) -> std::io::Result<[u8; 8]> { 65 | let mut r = [0u8; 8]; 66 | reader.read_exact(&mut r)?; 67 | Ok::<[u8; 8], std::io::Error>(r) 68 | } 69 | 70 | pub type Ob = std::sync::Arc>; 71 | 72 | pub fn new_ob(v: T) -> Ob { 73 | std::sync::Arc::new(std::cell::RefCell::new(v)) 74 | } 75 | 76 | pub fn read_u16(r: &mut dyn std::io::Read) -> std::io::Result { 77 | r.read_u16::() 78 | } 79 | 80 | pub fn read_u32(r: &mut dyn std::io::Read) -> std::io::Result { 81 | r.read_u32::() 82 | } 83 | 84 | pub fn read_u64(r: &mut dyn std::io::Read) -> std::io::Result { 85 | r.read_u64::() 86 | } 87 | 88 | pub fn write_u16(w: &mut dyn std::io::Write, v: u16) -> std::io::Result<()> { 89 | w.write_u16::(v) 90 | } 91 | 92 | pub fn write_u32(w: &mut dyn std::io::Write, v: u32) -> std::io::Result<()> { 93 | w.write_u32::(v) 94 | } 95 | 96 | pub fn write_u64(w: &mut dyn std::io::Write, v: u64) -> std::io::Result<()> { 97 | w.write_u64::(v) 98 | } 99 | 100 | pub fn seek(s: &mut dyn std::io::Seek, pos: u64) -> std::io::Result { 101 | s.seek(std::io::SeekFrom::Start(pos)) 102 | } 103 | 104 | 105 | // ====================================================================== 106 | 107 | pub mod test { 108 | 109 | use tempdir; 110 | 111 | pub fn dir() -> tempdir::TempDir { 112 | tempdir::TempDir::new("test").unwrap() 113 | } 114 | 115 | pub fn test_path(dir: &tempdir::TempDir, name: &str) -> String { 116 | String::from(dir.path().join(name).to_str().unwrap()) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/writer.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | 3 | use crate::storage; 4 | use crate::transaction; 5 | use crate::util; 6 | use crate::msg; 7 | use crate::msgmacros::*; 8 | 9 | macro_rules! respond { 10 | ($writer: expr, $id: expr, $data: expr) => ( 11 | $writer.write_all(&response!($id, $data)) 12 | .context("send response")? 13 | ) 14 | } 15 | 16 | macro_rules! error { 17 | ($writer: expr, $id: expr, $data: expr) => ( 18 | $writer.write_all(&error_response!($id, $data)) 19 | .context("send error response")? 20 | ) 21 | } 22 | 23 | macro_rules! async_ { 24 | ($writer: expr, $method: expr, $args: expr) => ( 25 | $writer.write_all(&message!(0, $method, ($args))) 26 | .context("send async")? 27 | ) 28 | } 29 | 30 | #[derive(Debug, Clone)] 31 | pub struct Client { 32 | name: String, 33 | send: std::sync::mpsc::Sender, 34 | request_id: i64, 35 | } 36 | 37 | impl Client { 38 | pub fn new(name: String, send: std::sync::mpsc::Sender) 39 | -> Client { 40 | Client {name: name, send: send, request_id: 0} 41 | } 42 | } 43 | 44 | impl PartialEq for Client { 45 | fn eq(&self, other: &Client) -> bool { 46 | self.name == other.name 47 | } 48 | } 49 | 50 | impl crate::storage::Client for Client { 51 | fn finished(&self, tid: &util::Tid, len: u64, size: u64) -> Result<()> { 52 | self.send.send( 53 | msg::Zeo::Finished(self.request_id, tid.clone(), len, size) 54 | ).context("send finished") 55 | } 56 | fn invalidate(&self, tid: &util::Tid, oids: &Vec) -> Result<()> { 57 | self.send.send(msg::Zeo::Invalidate( 58 | tid.clone(), oids.clone())).context("send invalidate") 59 | } 60 | fn close(&self) {} 61 | } 62 | 63 | struct TransactionsHolder<'store> { 64 | fs: std::sync::Arc>, 65 | transactions: std::collections::HashMap>, 66 | } 67 | 68 | impl<'store> Drop for TransactionsHolder<'store> { 69 | fn drop(&mut self) { 70 | for trans in self.transactions.values() { 71 | self.fs.tpc_abort(&trans.id); 72 | } 73 | } 74 | } 75 | 76 | pub fn writer( 77 | fs: std::sync::Arc>, 78 | mut writer: W, 79 | receiver: std::sync::mpsc::Receiver, 80 | client: Client) 81 | -> Result<()> { 82 | 83 | writer.write_all(&msg::size_vec(b"M5".to_vec())) 84 | .context("writing handshake")?; 85 | 86 | let mut transaction_holder = TransactionsHolder { 87 | fs: fs.clone(), 88 | transactions: std::collections::HashMap::new(), 89 | }; 90 | 91 | let transactions = &mut transaction_holder.transactions; 92 | 93 | for zeo in receiver.iter() { 94 | match zeo { 95 | msg::Zeo::Raw(bytes) => { 96 | writer.write_all(&bytes).context("writing raw")? 97 | }, 98 | msg::Zeo::TpcBegin(txn, user, desc, ext) => { 99 | if ! transactions.contains_key(&txn) { 100 | transactions.insert( 101 | txn, 102 | fs.tpc_begin(&user, &desc, &ext) 103 | .context("writer begin")?); 104 | } 105 | }, 106 | msg::Zeo::Storea(oid, serial, data, txn) => { 107 | if let Some(trans) = transactions.get_mut(&txn) { 108 | trans.save(oid, serial, &data) 109 | .context("writer save")?; 110 | } 111 | }, 112 | msg::Zeo::Vote(id, txn) => { 113 | if let Some(trans) = transactions.get(&txn) { 114 | let send = client.send.clone(); 115 | fs.lock(trans, Box::new( 116 | move | _ | send.send(msg::Zeo::Locked(id, txn)) 117 | .or::>(Ok(())) 118 | .unwrap() 119 | ))?; 120 | } 121 | else { 122 | error!(writer, id, 123 | ("ZODB.PosException.StorageTransactionError", 124 | "Invalid transaction")); 125 | }; 126 | }, 127 | msg::Zeo::Locked(id, txn) => { 128 | if let Some(mut trans) = transactions.get_mut(&txn) { 129 | trans.locked()?; 130 | let conflicts = fs.stage(&mut trans)?; 131 | let conflict_maps: 132 | Vec> = 133 | conflicts.iter() 134 | .map(| c | { 135 | let mut m: std::collections::BTreeMap< 136 | String, 137 | serde::bytes::Bytes, 138 | > = 139 | std::collections::BTreeMap::new(); 140 | m.insert("oid".to_string(), msg::bytes(&c.oid)); 141 | m.insert("serial".to_string(), msg::bytes(&c.serial)); 142 | m.insert("committed".to_string(), 143 | msg::bytes(&c.committed)); 144 | m.insert("data".to_string(), msg::bytes(&c.data)); 145 | m 146 | }) 147 | .collect(); 148 | respond!(writer, id, conflict_maps); 149 | } 150 | }, 151 | msg::Zeo::TpcFinish(id, txn) => { 152 | if let Some(trans) = transactions.remove(&txn) { 153 | let mut client = client.clone(); 154 | client.request_id = id; 155 | fs.tpc_finish(&trans.id, client)?; 156 | } 157 | else { 158 | error!(writer, id, 159 | ("ZODB.PosException.StorageTransactionError", 160 | "Invalid transaction")); 161 | } 162 | }, 163 | msg::Zeo::Finished(id, tid, len, size) => { 164 | respond!(writer, id, msg::bytes(&tid)); 165 | let mut info: std::collections::BTreeMap = 166 | std::collections::BTreeMap::new(); 167 | info.insert("length".to_string(), len); 168 | info.insert("size".to_string(), size); 169 | async_!(writer, "info", (info,)); 170 | }, 171 | msg::Zeo::Invalidate(tid, oids) => { 172 | let oids: Vec = 173 | oids.iter().map(| oid | msg::bytes(oid)).collect(); 174 | async_!(writer, "invalidateTransaction", (msg::bytes(&tid), oids)); 175 | }, 176 | msg::Zeo::TpcAbort(id, txn) => { 177 | if let Some(trans) = transactions.remove(&txn) { 178 | fs.tpc_abort(&trans.id); 179 | } 180 | respond!(writer, id, msg::NIL); 181 | 182 | }, 183 | msg::Zeo::End => break, 184 | _ => {} 185 | } 186 | } 187 | Ok(()) 188 | } 189 | -------------------------------------------------------------------------------- /tests/reader.rs: -------------------------------------------------------------------------------- 1 | // Test of the byteserver reader process 2 | use std::io::prelude::*; 3 | 4 | #[macro_use] 5 | extern crate byteserver; 6 | 7 | use std::collections::BTreeMap; 8 | 9 | use anyhow::Context; 10 | use byteorder::{ByteOrder, BigEndian}; 11 | use serde::bytes::ByteBuf; 12 | 13 | use byteserver::msg; 14 | use byteserver::msgmacros::*; 15 | use byteserver::util; 16 | use byteserver::reader; 17 | use byteserver::writer; 18 | use byteserver::storage; 19 | use byteserver::tid; 20 | 21 | fn unsize(mut v: Vec) -> Vec { 22 | assert_eq!(BigEndian::read_u32(&v), v.len() as u32 - 4); 23 | v.split_off(4) 24 | } 25 | 26 | #[test] 27 | fn basic() { 28 | let (reader, mut writer) = pipe::pipe(); 29 | let (tx, rx) = std::sync::mpsc::channel(); 30 | 31 | let tdir = byteserver::util::test::dir(); 32 | let path = byteserver::util::test::test_path(&tdir, "data.fs"); 33 | 34 | storage::testing::make_sample( 35 | &path, 36 | vec![vec![(util::Z64, b"000")], 37 | vec![(util::Z64, b"111"), (util::p64(3), b"ooo")], 38 | ], 39 | ).unwrap(); 40 | let fs = std::sync::Arc::new( 41 | storage::FileStorage::::open(path).unwrap()); 42 | let read_fs = fs.clone(); 43 | 44 | std::thread::spawn( 45 | move || reader::reader(read_fs, reader, tx).unwrap() 46 | ); 47 | 48 | // handshake 49 | writer.write_all(&msg::size_vec(b"M5".to_vec())).unwrap(); 50 | // register 51 | writer.write_all(&sencode!((1, "register", ("1", true))).unwrap()).unwrap(); 52 | // This generates a response directly 53 | match rx.recv().unwrap() { 54 | msg::Zeo::Raw(r) => { 55 | let r = unsize(r); 56 | let (id, code, tid): (u64, String, ByteBuf) = 57 | decode!(&mut (&r as &[u8]), 58 | "decoding register response").unwrap(); 59 | assert_eq!(id, 1); assert_eq!(&code, "R"); 60 | assert_eq!(util::read8(&mut (&*tid)).unwrap(), fs.last_transaction()); 61 | }, _ => panic!("invalid message") 62 | } 63 | // get_info(), mostly punt for now: 64 | writer.write_all(&sencode!((2, "get_info", ())).unwrap()).unwrap(); 65 | match rx.recv().unwrap() { 66 | msg::Zeo::Raw(r) => { 67 | let r = unsize(r); 68 | let (id, code, info): (u64, String, BTreeMap) = 69 | decode!(&mut (&r as &[u8]), 70 | "decoding get_info response").unwrap(); 71 | assert_eq!(id, 2); assert_eq!(&code, "R"); 72 | assert_eq!(info, BTreeMap::new()); 73 | }, _ => panic!("invalid message") 74 | } 75 | // loadBefore 76 | // current: 77 | let now = tid::next(&tid::now_tid()); 78 | writer.write_all( 79 | &sencode!((3, "loadBefore", (util::Z64, now))).unwrap()).unwrap(); 80 | let tid1 = match rx.recv().unwrap() { 81 | msg::Zeo::Raw(r) => { 82 | let r = unsize(r); 83 | let (id, code, (data, tid, end)): ( 84 | u64, String, (ByteBuf, ByteBuf, Option)) = 85 | decode!(&mut (&r as &[u8]), 86 | "decoding loadBefore response").unwrap(); 87 | assert_eq!(id, 3); assert_eq!(&code, "R"); 88 | assert_eq!(&*data, b"111"); 89 | assert!(end.is_none()); 90 | util::read8(&mut &*tid).unwrap() 91 | }, _ => panic!("invalid message") 92 | }; 93 | // previous 94 | writer.write_all( 95 | &sencode!((3, "loadBefore", (util::Z64, tid1))).unwrap()).unwrap(); 96 | let tid0 = match rx.recv().unwrap() { 97 | msg::Zeo::Raw(r) => { 98 | let r = unsize(r); 99 | let (id, code, (data, tid, end)): ( 100 | u64, String, (ByteBuf, ByteBuf, Option)) = 101 | decode!(&mut (&r as &[u8]), 102 | "decoding loadBefore response").unwrap(); 103 | assert_eq!(id, 3); assert_eq!(&code, "R"); 104 | assert_eq!(&*data, b"000"); 105 | assert_eq!(util::read8(&mut &*end.unwrap()).unwrap(), tid1); 106 | util::read8(&mut &*tid).unwrap() 107 | }, _ => panic!("invalid message") 108 | }; 109 | // pre creation 110 | writer.write_all( 111 | &sencode!((3, "loadBefore", (util::Z64, tid0))).unwrap()).unwrap(); 112 | match rx.recv().unwrap() { 113 | msg::Zeo::Raw(r) => { 114 | let r = unsize(r); 115 | let (id, code, n): (u64, String, Option) = 116 | decode!(&mut (&r as &[u8]), 117 | "decoding loadBefore response").unwrap(); 118 | assert_eq!(id, 3); assert_eq!(&code, "R"); 119 | assert!(n.is_none()); 120 | }, _ => panic!("invalid message") 121 | } 122 | // Error 123 | writer.write_all( 124 | &sencode!((3, "loadBefore", (util::p64(9), tid0))).unwrap()).unwrap(); 125 | match rx.recv().unwrap() { 126 | msg::Zeo::Raw(r) => { 127 | let r = unsize(r); 128 | let (id, code, (ename, (oid,))): ( 129 | u64, String, (String, (ByteBuf,))) = 130 | decode!(&mut (&r as &[u8]), 131 | "decoding loadBefore response").unwrap(); 132 | assert_eq!(id, 3); assert_eq!(&code, "E"); 133 | assert_eq!(ename, "ZODB.POSException.POSKeyError"); 134 | assert_eq!(&*oid, &util::p64(9)) 135 | }, _ => panic!("invalid message") 136 | } 137 | 138 | // Ping 139 | writer.write_all(&sencode!((4, "ping", ())).unwrap()).unwrap(); 140 | match rx.recv().unwrap() { 141 | msg::Zeo::Raw(r) => { 142 | let r = unsize(r); 143 | let (id, code, r): (u64, String, Option) = 144 | decode!(&mut (&r as &[u8]), 145 | "decoding ping response").unwrap(); 146 | assert_eq!(id, 4); assert_eq!(&code, "R"); 147 | assert!(r.is_none()); 148 | }, _ => panic!("invalid message") 149 | } 150 | 151 | // new_oids: 152 | writer.write_all(&sencode!((4, "new_oids", ())).unwrap()).unwrap(); 153 | match rx.recv().unwrap() { 154 | msg::Zeo::Raw(r) => { 155 | let r = unsize(r); 156 | let (id, code, oids): (u64, String, Vec) = 157 | decode!(&mut (&r as &[u8]), 158 | format!("decoding new_oids response {:?}", r)).unwrap(); 159 | assert_eq!(id, 4); assert_eq!(&code, "R"); 160 | assert_eq!( 161 | oids, 162 | (4..104) 163 | .map(| oid | ByteBuf::from(util::p64(oid).to_vec())) 164 | .collect::>() 165 | ) 166 | }, _ => panic!("invalid message") 167 | } 168 | 169 | // Requests that deal with transactions are merely forwarded: 170 | writer.write_all( 171 | &sencode!((0, "tpc_begin", (42, b"u", b"d", b"e", msg::NIL, b" "))) 172 | .unwrap()).unwrap(); 173 | match rx.recv().unwrap() { 174 | msg::Zeo::TpcBegin(42, user, desc, ext) => { 175 | assert_eq!((user, desc, ext), 176 | (b"u".to_vec(), b"d".to_vec(), b"e".to_vec())); 177 | }, _ => panic!("invalid message") 178 | } 179 | writer.write_all( 180 | &sencode!((0, "storea", (util::Z64, fs.last_transaction(), b"111", 42))) 181 | .unwrap()).unwrap(); 182 | match rx.recv().unwrap() { 183 | msg::Zeo::Storea(oid, serial, data, 42) => { 184 | assert_eq!((oid, serial, data), 185 | (util::Z64, fs.last_transaction(), b"111".to_vec())); 186 | }, _ => panic!("invalid message") 187 | } 188 | writer.write_all( 189 | &sencode!((4, "vote", (42,))).unwrap()).unwrap(); 190 | match rx.recv().unwrap() { 191 | msg::Zeo::Vote(4, 42) => { 192 | }, _ => panic!("invalid message") 193 | } 194 | writer.write_all( 195 | &sencode!((5, "tpc_finish", (42,))).unwrap()).unwrap(); 196 | match rx.recv().unwrap() { 197 | msg::Zeo::TpcFinish(5, 42) => { 198 | }, _ => panic!("invalid message") 199 | } 200 | writer.write_all( 201 | &sencode!((5, "tpc_abort", (42,))).unwrap()).unwrap(); 202 | match rx.recv().unwrap() { 203 | msg::Zeo::TpcAbort(5, 42) => { 204 | }, _ => panic!("invalid message") 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /tests/storage.rs: -------------------------------------------------------------------------------- 1 | extern crate byteserver; 2 | 3 | use anyhow::{Context, Result}; 4 | 5 | use byteserver::util; 6 | use byteserver::util::*; 7 | 8 | enum ClientMessage { 9 | Locked(Tid), 10 | Finished(Tid, u64, u64), 11 | Invalidate(Tid, Vec), 12 | } 13 | 14 | #[derive(Debug, Clone)] 15 | struct Client { 16 | name: String, 17 | send: std::sync::mpsc::Sender, 18 | } 19 | 20 | impl Client { 21 | fn new(name: &str) -> (Client, std::sync::mpsc::Receiver) { 22 | let (send, receive) = std::sync::mpsc::channel(); 23 | (Client { name: String::from(name), send: send }, receive) 24 | } 25 | } 26 | 27 | impl PartialEq for Client { 28 | fn eq(&self, other: &Client) -> bool { 29 | self.name == other.name 30 | } 31 | } 32 | 33 | impl byteserver::storage::Client for Client { 34 | fn finished(&self, tid: &Tid, len:u64, size: u64) -> Result<()> { 35 | self.send.send(ClientMessage::Finished(tid.clone(), len, size)) 36 | .context("") 37 | } 38 | fn invalidate(&self, tid: &Tid, oids: &Vec) -> Result<()> { 39 | self.send.send(ClientMessage::Invalidate( 40 | tid.clone(), oids.clone())).context("") 41 | } 42 | fn close(&self) {} 43 | } 44 | 45 | #[test] 46 | fn store() { 47 | 48 | let tmpdir = util::test::dir(); 49 | let fs = byteserver::storage::FileStorage::open( 50 | util::test::test_path(&tmpdir, "data.fs")).unwrap(); 51 | 52 | let (client, receive) = Client::new("0"); 53 | let mut clients = vec![Client::new("1"), Client::new("2")]; 54 | fs.add_client(client.clone()); 55 | for &(ref c, _) in clients.iter() { 56 | fs.add_client(c.clone()); 57 | } 58 | 59 | // First transaction: 60 | let mut trans = fs.tpc_begin(b"", b"", b"").unwrap(); 61 | trans.save(p64(0), Z64, b"zzzz").unwrap(); 62 | trans.save(p64(1), Z64, b"oooo").unwrap(); 63 | let tx = client.send.clone(); 64 | fs.lock(&trans, Box::new( 65 | move | id | tx.send(ClientMessage::Locked(id)).unwrap())).unwrap(); 66 | match receive.recv().unwrap() { 67 | ClientMessage::Locked(tid) => assert_eq!(tid, trans.id), 68 | _ => panic!("bad message"), 69 | } 70 | trans.locked().unwrap(); 71 | let conflicts = fs.stage(&mut trans).unwrap(); 72 | assert_eq!(conflicts.len(), 0); 73 | 74 | fs.tpc_finish(&trans.id, client.clone()).unwrap(); 75 | let tid0 = match receive.recv().unwrap() { 76 | ClientMessage::Finished(tid, len, size) => { 77 | assert_eq!(len, 2); 78 | assert_eq!(size, 4216); 79 | tid 80 | }, 81 | _ => panic!("bad message"), 82 | }; 83 | assert!(receive.try_recv().is_err()); 84 | for &(_, ref receive) in clients.iter() { 85 | match receive.recv().unwrap() { 86 | ClientMessage::Invalidate(tid, oids) => { 87 | assert_eq!(tid, tid0); 88 | assert_eq!(oids, vec![p64(0), p64(1)]); 89 | }, 90 | _ => panic!("bad message"), 91 | } 92 | assert!(receive.try_recv().is_err()); 93 | } 94 | assert_eq!(fs.last_transaction(), tid0); 95 | 96 | use byteserver::storage::LoadBeforeResult::*; 97 | 98 | let r = fs.load_before(&p64(1), &byteserver::tid::next(&tid0)).unwrap(); 99 | match r { 100 | Loaded(data, tid, None) => { 101 | assert_eq!(data, b"oooo".to_vec()); 102 | assert_eq!(tid, tid0); 103 | }, 104 | _ => panic!("unexpeted result {:?}", r), 105 | } 106 | 107 | // Drop one of the clients. We should see the storage client 108 | // count drop after it tries to send to them. 109 | clients.pop(); 110 | 111 | // Second, conflict and then success: 112 | let mut trans = fs.tpc_begin(b"", b"", b"").unwrap(); 113 | trans.save(p64(1), Z64, b"ooo1").unwrap(); 114 | let tx = client.send.clone(); 115 | fs.lock(&trans, Box::new( 116 | move | id | tx.send(ClientMessage::Locked(id)).unwrap())).unwrap(); 117 | match receive.recv().unwrap() { 118 | ClientMessage::Locked(tid) => assert_eq!(tid, trans.id), 119 | _ => panic!("bad message"), 120 | } 121 | trans.locked().unwrap(); 122 | let conflicts = fs.stage(&mut trans).unwrap(); 123 | 124 | use byteserver::storage::Conflict; 125 | assert_eq!( 126 | conflicts, 127 | vec![Conflict { oid: p64(1), serial: Z64, committed: tid0, 128 | data: b"ooo1".to_vec() }]); 129 | 130 | trans.save(p64(1), tid0, b"ooo2").unwrap(); 131 | let tx = client.send.clone(); 132 | fs.lock(&trans, Box::new( 133 | move | id | tx.send(ClientMessage::Locked(id)).unwrap())).unwrap(); 134 | match receive.recv().unwrap() { 135 | ClientMessage::Locked(tid) => assert_eq!(tid, trans.id), 136 | _ => panic!("bad message"), 137 | } 138 | trans.locked().unwrap(); 139 | let conflicts = fs.stage(&mut trans).unwrap(); 140 | assert_eq!(conflicts.len(), 0); 141 | 142 | fs.tpc_finish(&trans.id, client.clone()).unwrap(); 143 | let tid1 = match receive.recv().unwrap() { 144 | ClientMessage::Finished(tid, len, size) => { 145 | assert_eq!(len, 2); 146 | assert_eq!(size, 4296); 147 | tid 148 | }, 149 | _ => panic!("bad message"), 150 | }; 151 | assert!(receive.try_recv().is_err()); 152 | for &(_, ref receive) in clients.iter() { 153 | match receive.recv().unwrap() { 154 | ClientMessage::Invalidate(tid, oids) => { 155 | assert_eq!(tid, tid1); 156 | assert_eq!(oids, vec![p64(1)]); 157 | }, 158 | _ => panic!("bad message"), 159 | } 160 | assert!(receive.try_recv().is_err()); 161 | } 162 | assert_eq!(fs.last_transaction(), tid1); 163 | 164 | assert_eq!(fs.client_count(), 2); 165 | 166 | let r = fs.load_before(&p64(1), &tid1).unwrap(); 167 | match r { 168 | Loaded(data, tid, Some(end)) => { 169 | assert_eq!(data, b"oooo".to_vec()); 170 | assert_eq!(tid, tid0); 171 | assert_eq!(end, tid1); 172 | }, 173 | _ => panic!("unexpeted result {:?}", r), 174 | } 175 | 176 | let r = fs.load_before(&p64(1), &byteserver::tid::next(&tid1)).unwrap(); 177 | match r { 178 | Loaded(data, tid, None) => { 179 | assert_eq!(data, b"ooo2".to_vec()); 180 | assert_eq!(tid, tid1); 181 | }, 182 | _ => panic!("unexpeted result {:?}", r), 183 | } 184 | } 185 | 186 | #[test] 187 | fn abort() { 188 | 189 | let tmpdir = util::test::dir(); 190 | let fs = byteserver::storage::FileStorage::open( 191 | util::test::test_path(&tmpdir, "data.fs")).unwrap(); 192 | 193 | let (client, receive) = Client::new("0"); 194 | fs.add_client(client.clone()); 195 | 196 | let mut trans = fs.tpc_begin(b"", b"", b"").unwrap(); 197 | trans.save(p64(0), Z64, b"zzzz").unwrap(); 198 | let tx = client.send.clone(); 199 | fs.lock(&trans, Box::new( 200 | move | id | tx.send(ClientMessage::Locked(id)).unwrap())).unwrap(); 201 | match receive.recv().unwrap() { 202 | ClientMessage::Locked(tid) => assert_eq!(tid, trans.id), 203 | _ => panic!("bad message"), 204 | } 205 | trans.locked().unwrap(); 206 | 207 | // Abort releases the lock, so we can start over: 208 | fs.tpc_abort(&trans.id); 209 | 210 | let mut trans = fs.tpc_begin(b"", b"", b"").unwrap(); 211 | trans.save(p64(0), Z64, b"zzzz").unwrap(); 212 | let tx = client.send.clone(); 213 | fs.lock(&trans, Box::new( 214 | move | id | tx.send(ClientMessage::Locked(id)).unwrap())).unwrap(); 215 | match receive.recv().unwrap() { 216 | ClientMessage::Locked(tid) => assert_eq!(tid, trans.id), 217 | _ => panic!("bad message"), 218 | } 219 | trans.locked().unwrap(); 220 | let conflicts = fs.stage(&mut trans).unwrap(); 221 | assert_eq!(conflicts.len(), 0); 222 | fs.tpc_abort(&trans.id); 223 | 224 | // Abort releases locks *and* prevents the transaction from committing. 225 | 226 | // We'll go again, which would fail if the previous attempts had 227 | // committed: 228 | 229 | let mut trans = fs.tpc_begin(b"", b"", b"").unwrap(); 230 | trans.save(p64(0), Z64, b"zzzz").unwrap(); 231 | let tx = client.send.clone(); 232 | fs.lock(&trans, Box::new( 233 | move | id | tx.send(ClientMessage::Locked(id)).unwrap())).unwrap(); 234 | match receive.recv().unwrap() { 235 | ClientMessage::Locked(tid) => assert_eq!(tid, trans.id), 236 | _ => panic!("bad message"), 237 | } 238 | trans.locked().unwrap(); 239 | let conflicts = fs.stage(&mut trans).unwrap(); 240 | assert_eq!(conflicts.len(), 0); 241 | fs.tpc_finish(&trans.id, client.clone()).unwrap(); 242 | match receive.recv().unwrap() { 243 | ClientMessage::Finished(_, _, _) => { 244 | }, 245 | _ => panic!("bad message"), 246 | } 247 | assert!(receive.try_recv().is_err()); 248 | } 249 | -------------------------------------------------------------------------------- /tests/writer.rs: -------------------------------------------------------------------------------- 1 | // Test the writer half of the server 2 | 3 | #[macro_use] 4 | extern crate byteserver; 5 | 6 | use std::collections::BTreeMap; 7 | 8 | use anyhow::Context; 9 | use serde::bytes::ByteBuf; 10 | 11 | use byteserver::msg; 12 | use byteserver::msgmacros::*; 13 | use byteserver::util; 14 | use byteserver::writer; 15 | use byteserver::storage; 16 | 17 | #[test] 18 | fn basic() { 19 | let (reader, writer) = pipe::pipe(); 20 | let (tx, rx) = std::sync::mpsc::channel(); 21 | 22 | let tdir = byteserver::util::test::dir(); 23 | let path = byteserver::util::test::test_path(&tdir, "data.fs"); 24 | 25 | storage::testing::make_sample( 26 | &path, vec![vec![(util::Z64, b"000")], vec![(util::Z64, b"111")]]).unwrap(); 27 | let fs = std::sync::Arc::new( 28 | storage::FileStorage::::open(path).unwrap()); 29 | 30 | let client = writer::Client::new("test".to_string(), tx.clone()); 31 | fs.add_client(client.clone()); 32 | let write_fs = fs.clone(); 33 | let write_client = client.clone(); 34 | std::thread::spawn( 35 | move || writer::writer(write_fs, writer, rx, write_client).unwrap()); 36 | 37 | let mut reader = msg::ZeoIter::new(reader); 38 | 39 | // Handshake: 40 | assert_eq!(&reader.next_vec().unwrap(), b"M5"); 41 | 42 | // Lets write some data: 43 | tx.send(msg::Zeo::TpcBegin(42, b"u".to_vec(), b"d".to_vec(), b"{}".to_vec())) 44 | .unwrap(); 45 | tx.send(msg::Zeo::Storea(util::p64(1), util::Z64, b"ooo".to_vec(), 42)).unwrap(); 46 | tx.send(msg::Zeo::Vote(11, 42)).unwrap(); 47 | 48 | // We get back any conflicts: 49 | let (msgid, flag, conflicts): ( 50 | i64, String, Vec>) = 51 | decode!(&mut (&reader.next_vec().unwrap() as &[u8]), 52 | "decoding conflicts").unwrap(); 53 | assert_eq!((msgid, &flag as &str), (11, "R")); 54 | // There weren't any: 55 | assert_eq!(conflicts.len(), 0); 56 | 57 | // And we finish, getting back a tid and info: 58 | tx.send(msg::Zeo::TpcFinish(12, 42)).unwrap(); 59 | let (msgid, flag, tid): (i64, String, ByteBuf) = 60 | decode!(&mut (&reader.next_vec().unwrap() as &[u8]), 61 | "decoding finish response").unwrap(); 62 | assert_eq!((msgid, &flag as &str), (12, "R")); 63 | assert_eq!(tid.len(), 8); 64 | let (msgid, method, (info,)): (i64, String, (BTreeMap,)) = 65 | decode!(&mut (&reader.next_vec().unwrap() as &[u8]), 66 | "decoding info").unwrap(); 67 | assert_eq!((msgid, &method as &str), (0, "info")); 68 | assert_eq!(info, { 69 | let mut map = BTreeMap::new(); 70 | map.insert("length".to_string(), 2); 71 | map.insert("size".to_string(), 4337); 72 | map 73 | }); 74 | 75 | if let storage::LoadBeforeResult::Loaded(data, ltid, end) = 76 | fs.load_before(&util::p64(1), storage::testing::MAXTID).unwrap() { 77 | assert_eq!(<id, &*tid); 78 | assert_eq!(&data, b"ooo"); 79 | assert!(end.is_none()); 80 | } 81 | else { panic!("Couldn't load") } 82 | 83 | // If data are updated not by the client, we'll be notified: 84 | let (tx2, _) = std::sync::mpsc::channel(); 85 | let client2 = writer::Client::new("test2".to_string(), tx2.clone()); 86 | storage::testing::add_data(&fs, &client2, vec![vec![(util::p64(3), b"ttt")]]) 87 | .context("adding data").unwrap(); 88 | let (msgid, method, (itid, oids)): (i64, String, (ByteBuf, Vec)) = 89 | decode!(&mut (&reader.next_vec().unwrap() as &[u8]), 90 | "decoding invalidations").unwrap(); 91 | assert_eq!((msgid, &method as &str), (0, "invalidateTransaction")); 92 | assert_eq!(itid.len(), 8); 93 | assert!(itid > tid); 94 | assert_eq!(oids, vec![ByteBuf::from(util::p64(3).to_vec())]); 95 | } 96 | -------------------------------------------------------------------------------- /to-do.rst: -------------------------------------------------------------------------------- 1 | ====== 2 | To dos 3 | ====== 4 | 5 | - Most functionality beyond simple store/load. :) pack, replication, etc. 6 | 7 | - Something to check for invalid data. 8 | 9 | - Lock/vote timeouts. (Probably using the ``timer`` crate.) 10 | 11 | 12 | 13 | 14 | - Better unmarshalling errors. tpc_begin error -> client waiting for vote. 15 | Basically, errors in async methods on server should disconnect. 16 | 17 | - server logging 18 | 19 | - Client gets invalis transaction error after reconnecting while 20 | waiting for vote response. It should already have aborted. 21 | 22 | --------------------------------------------------------------------------------