2
2
use std:: {
3
3
hash:: { Hash , Hasher } ,
4
4
mem,
5
- sync:: Mutex ,
5
+ sync:: { Arc , Mutex } ,
6
6
} ;
7
7
8
+ use concurrency:: Notification ;
8
9
use hashbrown:: HashTable ;
9
10
use numeric_id:: { define_id, IdVec , NumericId } ;
10
11
use once_cell:: sync:: Lazy ;
@@ -260,22 +261,23 @@ impl IndexBase for ColumnIndex {
260
261
}
261
262
} ;
262
263
263
- THREAD_POOL . install ( || {
264
- rayon:: scope ( |scope | {
264
+ run_in_thread_pool_and_block ( & THREAD_POOL , || {
265
+ rayon:: in_place_scope ( |inner | {
265
266
let mut cur = Offset :: new ( 0 ) ;
266
267
loop {
267
268
let mut buf = TaggedRowBuffer :: new ( cols. len ( ) ) ;
268
269
if let Some ( next) =
269
270
table. scan_project ( subset, cols, cur, BATCH_SIZE , & [ ] , & mut buf)
270
271
{
271
272
cur = next;
272
- scope . spawn ( move |_| split_buf ( buf) ) ;
273
+ inner . spawn ( move |_| split_buf ( buf) ) ;
273
274
} else {
274
- scope . spawn ( move |_| split_buf ( buf) ) ;
275
+ inner . spawn ( move |_| split_buf ( buf) ) ;
275
276
break ;
276
277
}
277
278
}
278
279
} ) ;
280
+
279
281
self . shards . par_iter_mut ( ) . for_each ( |( shard_id, shard) | {
280
282
use indexmap:: map:: Entry ;
281
283
// Sort the vector by start row id to ensure we populate subsets in sorted order.
@@ -302,6 +304,43 @@ impl IndexBase for ColumnIndex {
302
304
}
303
305
}
304
306
307
+ /// This function is an alternative for [`rayon::ThreadPool::install`] that doesn't steal work from
308
+ /// the callee's current thread pool while waiting for `f` to finish.
309
+ ///
310
+ /// We do this to avoid deadlocks. The whole purpose of using a separate threadpool in this module
311
+ /// is to allow for sufficient parallelism while holding a lock on the main threadpool. That means
312
+ /// we are not worried about an outer lock tying up a thread in the main pool.
313
+ ///
314
+ /// On the other hand, it _is_ a bad idea to steal work on a rayon thread pool with some locks
315
+ /// held. In particular, if another task on the thread pool _itself_ attempts to aquire the same
316
+ /// lock, this can cause a deadlock. We saw this in the tests for this crate. The relevant lock
317
+ /// are those around individual indexes stored in the database-level index cache.
318
+ fn run_in_thread_pool_and_block < ' a > ( pool : & rayon:: ThreadPool , f : impl FnMut ( ) + Send + ' a ) {
319
+ // NB: We don't need the heap allocations here. But we are only calling this function if
320
+ // we are about to do a bunch of work, so clarify is probably going to be better than (even
321
+ // more) unsafe code.
322
+
323
+ // Alright, here we go: pretend `f` has `'static` lifetime because we are passing it to
324
+ // `spawn`.
325
+ trait LifetimeWork < ' a > : FnMut ( ) + Send + ' a { }
326
+
327
+ impl < ' a , F : FnMut ( ) + Send + ' a > LifetimeWork < ' a > for F { }
328
+ let as_lifetime: Box < dyn LifetimeWork < ' a > > = Box :: new ( f) ;
329
+ let mut casted_away = unsafe {
330
+ // SAFETY: `casted_away` will be dropped at the end of this method. The notification used
331
+ // below will ensure it does not escape.
332
+ mem:: transmute :: < Box < dyn LifetimeWork < ' a > > , Box < dyn LifetimeWork < ' static > > > ( as_lifetime)
333
+ } ;
334
+ let n = Arc :: new ( Notification :: new ( ) ) ;
335
+ let inner = n. clone ( ) ;
336
+ pool. spawn ( move || {
337
+ casted_away ( ) ;
338
+ mem:: drop ( casted_away) ;
339
+ inner. notify ( ) ;
340
+ } ) ;
341
+ n. wait ( )
342
+ }
343
+
305
344
impl ColumnIndex {
306
345
pub ( crate ) fn new ( ) -> ColumnIndex {
307
346
with_pool_set ( |ps| {
@@ -447,7 +486,7 @@ impl IndexBase for TupleIndex {
447
486
queues[ shard_id] . lock ( ) . unwrap ( ) . push ( ( first, buf) ) ;
448
487
}
449
488
} ;
450
- THREAD_POOL . install ( || {
489
+ run_in_thread_pool_and_block ( & THREAD_POOL , || {
451
490
rayon:: scope ( |scope| {
452
491
let mut cur = Offset :: new ( 0 ) ;
453
492
loop {
0 commit comments