@@ -302,47 +302,47 @@ rather pointless as a finalizer). This leads us to a bit of a conundrum.
302302There are a few approaches to dealing with this problem:
303303
3043041 . 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.
310310
3113112 . 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+ ```
336336
3373373 . 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