@@ -302,47 +302,47 @@ rather pointless as a finalizer). This leads us to a bit of a conundrum.
302
302
There are a few approaches to dealing with this problem:
303
303
304
304
1 . When single-threaded, code could call the internal ` jl_gc_enable_finalizers `
305
- C function to prevent finalizers from being scheduled
306
- inside a critical region. Internally, this is used inside some functions (such
307
- as our C locks) to prevent recursion when doing certain operations (incremental
308
- package loading, codegen, etc.). The combination of a lock and this flag
309
- can be used to make finalizers safe.
305
+ C function to prevent finalizers from being scheduled
306
+ inside a critical region. Internally, this is used inside some functions (such
307
+ as our C locks) to prevent recursion when doing certain operations (incremental
308
+ package loading, codegen, etc.). The combination of a lock and this flag
309
+ can be used to make finalizers safe.
310
310
311
311
2 . A second strategy, employed by Base in a couple places, is to explicitly
312
- delay a finalizer until it may be able to acquire its lock non-recursively.
313
- The following example demonstrates how this strategy could be applied to
314
- ` Distributed.finalize_ref ` :
315
-
316
- ```
317
- function finalize_ref(r::AbstractRemoteRef)
318
- if r.where > 0 # Check if the finalizer is already run
319
- if islocked(client_refs) || !trylock(client_refs)
320
- # delay finalizer for later if we aren't free to acquire the lock
321
- finalizer(finalize_ref, r)
322
- return nothing
323
- end
324
- try # `lock` should always be followed by `try`
325
- if r.where > 0 # Must check again here
326
- # Do actual cleanup here
327
- r.where = 0
328
- end
329
- finally
330
- unlock(client_refs)
331
- end
332
- end
333
- nothing
334
- end
335
- ```
312
+ delay a finalizer until it may be able to acquire its lock non-recursively.
313
+ The following example demonstrates how this strategy could be applied to
314
+ ` Distributed.finalize_ref ` :
315
+
316
+ ```
317
+ function finalize_ref(r::AbstractRemoteRef)
318
+ if r.where > 0 # Check if the finalizer is already run
319
+ if islocked(client_refs) || !trylock(client_refs)
320
+ # delay finalizer for later if we aren't free to acquire the lock
321
+ finalizer(finalize_ref, r)
322
+ return nothing
323
+ end
324
+ try # `lock` should always be followed by `try`
325
+ if r.where > 0 # Must check again here
326
+ # Do actual cleanup here
327
+ r.where = 0
328
+ end
329
+ finally
330
+ unlock(client_refs)
331
+ end
332
+ end
333
+ nothing
334
+ end
335
+ ```
336
336
337
337
3 . A related third strategy is to use a yield-free queue. We don't currently
338
- have a lock-free queue implemented in Base, but
339
- ` Base.InvasiveLinkedListSynchronized{T} ` is suitable. This can frequently be a
340
- good strategy to use for code with event loops. For example, this strategy is
341
- employed by ` Gtk.jl ` to manage lifetime ref-counting. In this approach, we
342
- don't do any explicit work inside the ` finalizer ` , and instead add it to a queue
343
- to run at a safer time. In fact, Julia's task scheduler already uses this, so
344
- defining the finalizer as ` x -> @spawn do_cleanup(x) ` is one example of this
345
- approach. Note however that this doesn't control which thread ` do_cleanup `
346
- runs on, so ` do_cleanup ` would still need to acquire a lock. That
347
- doesn't need to be true if you implement your own queue, as you can explicitly
348
- only drain that queue from your thread.
338
+ have a lock-free queue implemented in Base, but
339
+ ` Base.InvasiveLinkedListSynchronized{T} ` is suitable. This can frequently be a
340
+ good strategy to use for code with event loops. For example, this strategy is
341
+ employed by ` Gtk.jl ` to manage lifetime ref-counting. In this approach, we
342
+ don't do any explicit work inside the ` finalizer ` , and instead add it to a queue
343
+ to run at a safer time. In fact, Julia's task scheduler already uses this, so
344
+ defining the finalizer as ` x -> @spawn do_cleanup(x) ` is one example of this
345
+ approach. Note however that this doesn't control which thread ` do_cleanup `
346
+ runs on, so ` do_cleanup ` would still need to acquire a lock. That
347
+ doesn't need to be true if you implement your own queue, as you can explicitly
348
+ only drain that queue from your thread.
0 commit comments