@@ -5,8 +5,8 @@ use std::marker::PhantomData;
5
5
use bincode:: { Decode , Encode , config:: standard} ;
6
6
use byteorder:: LE ;
7
7
use futures:: channel:: oneshot:: { self , Canceled } ;
8
- use heed:: { BytesDecode , BytesEncode , types :: U64 , Env } ;
9
- use tracing:: { trace, warn} ;
8
+ use heed:: { types :: U64 , BytesDecode , BytesEncode , Env , MdbError } ;
9
+ use tracing:: { info , trace, warn} ;
10
10
11
11
use crate :: {
12
12
database:: Database ,
@@ -15,7 +15,7 @@ use crate::{
15
15
world:: chunk_format:: Chunk
16
16
} ;
17
17
18
- use super :: LMDB_THREADPOOL ;
18
+ use super :: { LMDB_PAGE_SIZE , LMDB_PAGE_SIZE_INCREMENT , LMDB_READER_SYNC , LMDB_THREADPOOL } ;
19
19
20
20
pub struct Zstd < T > ( PhantomData < T > ) ;
21
21
@@ -28,6 +28,7 @@ impl<'a, T: Encode + 'a> BytesEncode<'a> for Zstd<T> {
28
28
let mut bytes = Vec :: new ( ) ;
29
29
let mut compressor = zstd:: Encoder :: new ( & mut bytes, 6 ) ?;
30
30
bincode:: encode_into_std_write ( item, & mut compressor, standard ( ) ) ?;
31
+ compressor. finish ( ) ?;
31
32
32
33
Ok ( Cow :: Owned ( bytes) )
33
34
}
@@ -44,17 +45,52 @@ impl<'a, T: Decode + 'a> BytesDecode<'a> for Zstd<T> {
44
45
}
45
46
}
46
47
48
+ /// LMDB will follow a linear growth as opposed to MDBX which
49
+ /// uses a geometric growth.
50
+ pub ( super ) fn new_page_size ( old_size : usize ) -> usize {
51
+ old_size + LMDB_PAGE_SIZE_INCREMENT
52
+ }
53
+
47
54
// Will delegate a database operation to the database threadpool
48
- pub ( super ) fn spawn_blocking_db < F , R > ( f : F ) -> impl Future < Output = Result < R , Canceled > >
55
+ pub ( super ) fn spawn_blocking_db < F , R > ( db : Env , f : F ) -> impl Future < Output = Result < Result < R , heed :: Error > , Canceled > >
49
56
where
50
- F : FnOnce ( ) -> R + Send + ' static ,
51
- R : Send + ' static ,
57
+ F : Fn ( ) -> Result < R , heed :: Error > + Send + ' static ,
58
+ R : Send + ' static + std :: fmt :: Debug ,
52
59
{
53
- let ( tx, res) = oneshot:: channel :: < R > ( ) ;
60
+ let ( tx, res) = oneshot:: channel :: < Result < R , heed :: Error > > ( ) ;
54
61
55
62
let pool = LMDB_THREADPOOL . get ( ) . unwrap ( ) ;
56
63
pool. spawn ( move || {
57
- if tx. send ( f ( ) ) . is_err ( ) {
64
+
65
+ let read_lock = LMDB_READER_SYNC . read ( )
66
+ . expect ( "Database RWLock has been poisoned. A thread should have crashed somewhere." ) ;
67
+
68
+ let mut res = f ( ) ;
69
+ if let Err ( heed:: Error :: Mdb ( MdbError :: MapFull ) ) = res {
70
+
71
+ tracing:: warn!( "Database page is full. Resizing..." ) ;
72
+
73
+ drop ( read_lock) ;
74
+
75
+ let _resize_guard = LMDB_READER_SYNC . write ( )
76
+ . expect ( "Database RWLock has been poisoned. A thread should have crashed somewhere." ) ;
77
+
78
+ let mut global_size_lock = LMDB_PAGE_SIZE . lock ( ) . unwrap ( ) ;
79
+ let old_size = * global_size_lock;
80
+ * global_size_lock = new_page_size ( old_size) ;
81
+ unsafe { db. resize ( * global_size_lock) . expect ( "Unable to resize LMDB environment." ) } ;
82
+
83
+ tracing:: info!( "Successfully resized LMDB page from {} MiB to {} MiB" , ( old_size / 1024usize . pow( 2 ) ) , ( * global_size_lock / 1024usize . pow( 2 ) ) ) ;
84
+
85
+ drop ( global_size_lock) ;
86
+ drop ( _resize_guard) ;
87
+
88
+ res = f ( ) ;
89
+ } else {
90
+ drop ( read_lock)
91
+ }
92
+
93
+ if tx. send ( res) . is_err ( ) {
58
94
tracing:: warn!( "A database task has been unable to send its result because the receiver at other end have closed." )
59
95
}
60
96
} ) ;
@@ -71,7 +107,7 @@ impl Database {
71
107
}
72
108
73
109
/// Fetch chunk from database
74
- fn get_chunk_from_database ( db : & Env , key : & u64 ) -> Result < Option < Chunk > , Error > {
110
+ fn get_chunk_from_database ( db : & Env , key : & u64 ) -> Result < Option < Chunk > , heed :: Error > {
75
111
// Initialize read transaction and open chunks table
76
112
let ro_tx = db. read_txn ( ) ?;
77
113
let database = db
@@ -80,11 +116,11 @@ impl Database {
80
116
81
117
// Attempt to fetch chunk from table
82
118
database. get ( & ro_tx, key)
83
- . map_err ( |err| Error :: DatabaseError ( format ! ( "Failed to get chunk: {err}" ) ) )
119
+ // .map_err(|err| Error::DatabaseError(format!("Failed to get chunk: {err}")))
84
120
}
85
121
86
122
/// Insert a single chunk into database
87
- fn insert_chunk_into_database ( db : & Env , chunk : & Chunk ) -> Result < ( ) , Error > {
123
+ fn insert_chunk_into_database ( db : & Env , chunk : & Chunk ) -> Result < ( ) , heed :: Error > {
88
124
// Initialize write transaction and open chunks table
89
125
let mut rw_tx = db. write_txn ( ) ?;
90
126
let database = db
@@ -96,22 +132,24 @@ impl Database {
96
132
97
133
// Insert chunk
98
134
let res = database. put ( & mut rw_tx, & key, chunk) ;
99
- rw_tx. commit ( ) . map_err ( |err| {
100
- Error :: DatabaseError ( format ! ( "Unable to commit changes to database: {err}" ) )
101
- } ) ?;
102
-
103
- if let Err ( err) = res {
104
- Err ( Error :: DatabaseError ( format ! (
105
- "Failed to insert or update chunk: {err}"
106
- ) ) )
107
- } else {
108
- Ok ( ( ) )
109
- }
135
+ rw_tx. commit ( ) ?;
136
+ // .map_err(|err| {
137
+ // Error::DatabaseError(format!("Unable to commit changes to database: {err}"))
138
+ // })?;
139
+
140
+ res
141
+ // if let Err(err) = res {
142
+ // Err(Error::DatabaseError(format!(
143
+ // "Failed to insert or update chunk: {err}"
144
+ // )))
145
+ // } else {
146
+ // Ok(())
147
+ // }
110
148
}
111
149
112
150
/// Insert multiple chunks into database
113
151
/// TODO: Find better name/disambiguation
114
- fn insert_chunks_into_database ( db : & Env , chunks : & [ Chunk ] ) -> Result < ( ) , Error > {
152
+ fn insert_chunks_into_database ( db : & Env , chunks : & [ Chunk ] ) -> Result < ( ) , heed :: Error > {
115
153
// Initialize write transaction and open chunks table
116
154
let mut rw_tx = db. write_txn ( ) ?;
117
155
let database = db
@@ -124,20 +162,23 @@ impl Database {
124
162
let key = hash ( ( chunk. dimension . as_ref ( ) . unwrap ( ) , chunk. x_pos , chunk. z_pos ) ) ;
125
163
126
164
// Insert chunk
127
- database. put ( & mut rw_tx, & key, chunk) . map_err ( |err| {
128
- Error :: DatabaseError ( format ! ( "Failed to insert or update chunk: {err}" ) )
129
- } ) ?;
165
+ database. put ( & mut rw_tx, & key, chunk) ?
166
+ // .map_err(|err| {
167
+ // Error::DatabaseError(format!("Failed to insert or update chunk: {err}"))
168
+ // })?;
130
169
}
131
170
132
171
// Commit changes
133
- rw_tx. commit ( ) . map_err ( |err| {
134
- Error :: DatabaseError ( format ! ( "Unable to commit changes to database: {err}" ) )
135
- } ) ?;
172
+ rw_tx. commit ( ) ?;
173
+ // .map_err(|err| {
174
+ // Error::DatabaseError(format!("Unable to commit changes to database: {err}"))
175
+ // })?;
136
176
Ok ( ( ) )
137
177
}
138
178
139
179
async fn load_into_cache ( & self , key : u64 ) -> Result < ( ) , Error > {
140
180
let db = self . db . clone ( ) ;
181
+ let tsk_db = self . db . clone ( ) ;
141
182
let cache = self . cache . clone ( ) ;
142
183
143
184
tokio:: task:: spawn ( async move {
@@ -148,7 +189,7 @@ impl Database {
148
189
}
149
190
// If not in cache then search in database
150
191
else if let Ok ( chunk) =
151
- spawn_blocking_db ( move || Self :: get_chunk_from_database ( & db, & key) )
192
+ spawn_blocking_db ( tsk_db , move || Self :: get_chunk_from_database ( & db, & key) )
152
193
. await
153
194
. unwrap ( )
154
195
{
@@ -196,7 +237,8 @@ impl Database {
196
237
// Insert chunk into persistent database
197
238
let chunk = value. clone ( ) ;
198
239
let db = self . db . clone ( ) ;
199
- spawn_blocking_db ( move || Self :: insert_chunk_into_database ( & db, & chunk) )
240
+ let tsk_db = self . db . clone ( ) ;
241
+ spawn_blocking_db ( tsk_db, move || Self :: insert_chunk_into_database ( & db, & chunk) )
200
242
. await
201
243
. unwrap ( ) ?;
202
244
@@ -233,14 +275,15 @@ impl Database {
233
275
) -> Result < Option < Chunk > , Error > {
234
276
// Calculate key of this chunk and clone database pointer
235
277
let key = hash ( ( dimension, x, z) ) ;
278
+ let tsk_db = self . db . clone ( ) ;
236
279
let db = self . db . clone ( ) ;
237
280
238
281
// First check cache
239
282
if self . cache . contains_key ( & key) {
240
283
Ok ( self . cache . get ( & key) . await )
241
284
}
242
285
// Attempt to get chunk from persistent database
243
- else if let Some ( chunk) = spawn_blocking_db ( move || Self :: get_chunk_from_database ( & db, & key) )
286
+ else if let Some ( chunk) = spawn_blocking_db ( tsk_db , move || Self :: get_chunk_from_database ( & db, & key) )
244
287
. await
245
288
. unwrap ( ) ?
246
289
{
@@ -274,14 +317,15 @@ impl Database {
274
317
pub async fn chunk_exists ( & self , x : i32 , z : i32 , dimension : String ) -> Result < bool , Error > {
275
318
// Calculate key and copy database pointer
276
319
let key = hash ( ( dimension, x, z) ) ;
320
+ let tsk_db = self . db . clone ( ) ;
277
321
let db = self . db . clone ( ) ;
278
322
279
323
// Check first cache
280
324
if self . cache . contains_key ( & key) {
281
325
Ok ( true )
282
326
// Else check persistent database and load it into cache
283
327
} else {
284
- let res = spawn_blocking_db ( move || Self :: get_chunk_from_database ( & db, & key) ) . await . unwrap ( ) ;
328
+ let res = spawn_blocking_db ( tsk_db , move || Self :: get_chunk_from_database ( & db, & key) ) . await . unwrap ( ) ;
285
329
286
330
// WARNING: The previous logic was to order the chunk to be loaded into cache whether it existed or not.
287
331
// This has been replaced by directly loading the queried chunk into cache
@@ -293,7 +337,7 @@ impl Database {
293
337
}
294
338
Ok ( exist)
295
339
}
296
- Err ( err) => Err ( err) ,
340
+ Err ( err) => Err ( Error :: LmdbError ( err) ) ,
297
341
}
298
342
}
299
343
}
@@ -324,7 +368,8 @@ impl Database {
324
368
// Insert new chunk state into persistent database
325
369
let chunk = value. clone ( ) ;
326
370
let db = self . db . clone ( ) ;
327
- spawn_blocking_db ( move || Self :: insert_chunk_into_database ( & db, & chunk) ) . await . unwrap ( ) ?;
371
+ let tsk_db = self . db . clone ( ) ;
372
+ spawn_blocking_db ( tsk_db, move || Self :: insert_chunk_into_database ( & db, & chunk) ) . await . unwrap ( ) ?;
328
373
329
374
// Insert new chunk state into cache
330
375
self . cache . insert ( key, value) . await ;
@@ -411,11 +456,12 @@ impl Database {
411
456
412
457
// Clone database pointer
413
458
let db = self . db . clone ( ) ;
459
+ let tsk_db = self . db . clone ( ) ;
414
460
415
461
// Calculate all keys
416
462
let keys = values
417
463
. iter ( )
418
- . map ( |v| hash ( ( v. dimension . as_ref ( ) . expect ( format ! ( "Invalid chunk @ ({},{})" , v. x_pos, v. z_pos) . as_str ( ) ) , v. x_pos , v. z_pos ) ) )
464
+ . map ( |v| hash ( ( v. dimension . as_ref ( ) . unwrap_or_else ( || panic ! ( "Invalid chunk @ ({},{})" , v. x_pos, v. z_pos) ) , v. x_pos , v. z_pos ) ) )
419
465
. collect :: < Vec < u64 > > ( ) ;
420
466
421
467
// WARNING: The previous logic was to first insert in database and then insert in cache using load_into_cache fn.
@@ -425,11 +471,12 @@ impl Database {
425
471
self . cache . insert ( key, chunk. clone ( ) ) . await ;
426
472
self . load_into_cache ( key) . await ?;
427
473
}
428
-
474
+
429
475
// Then insert into persistent database
430
- spawn_blocking_db ( move || Self :: insert_chunks_into_database ( & db, & values) )
476
+ spawn_blocking_db ( tsk_db , move || Self :: insert_chunks_into_database ( & db, & values) )
431
477
. await
432
478
. unwrap ( ) ?;
479
+
433
480
Ok ( ( ) )
434
481
}
435
482
}
0 commit comments