This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author nirai
Recipients bobbyi, gregory.p.smith, neologix, nirai, pitrou, sdaoden, vstinner
Date 2011-05-14.18:54:56
SpamBayes Score 0.0
Marked as misclassified No
Message-id <1305399297.63.0.0655124420366.issue6721@psf.upfronthosting.co.za>
In-reply-to
Content
I think that generally it is better to deadlock than corrupt data.

> 2) acquiring locks just before fork is probably one of the best way to
> deadlock (acquiring a lock we already hold, or acquiring a lock needed
> by another thread before it releases its own lock). Apart from adding
> dealock avoidance/recovery mechanisms - which would be far from
> trivial - I don't see how we could solve this, given that each library
> can use its own locks, not counting the user-created ones

a) We know the correct locking order in Python's std libraries so the problem there is kind of solved.

b) We can put the burden of other locks on application developers and since currently no one registers atfork handlers, there is no problem there yet.

> 4) Python locks differ from usual mutexes/semaphores in that they can
> be held for quite some time (for example while performing I/O). Thus,
> acquiring all the locks could take a long time, and users might get
> irritated if fork takes 2 seconds to complete.

We only need a prepare handler to acquire locks that protect data from corruption.

A lock synchronizing IO which is held for long periods may possibly be initialized in child without being acquired in a prepare handler; for example, a lock serializing logging messages.

In other cases or in general an atfork handler may reset or reinitialize a library without acquiring locks in a prepare handler.

> 5) Finally, there's a fundamental problem with this approach, because
> Python locks can be released by a thread other than the one that owns
> it.
> Imagine this happens:
>
> T1                         T2
>                          lock.acquire()
>                          (do something without releasing lock)
> fork()
> lock.release()
>
> This is perfectly valid with the current lock implementation (for
> example, it can be used to implement a rendez-vous point so that T2
> doesn't start processing before T1 forked worker processes, or
> whatever).
> But if T1 tries to acquire lock (held by T2) before fork, then it will
> deadlock, since it will never be release by T2.

I think we do not need to acquire rendezvous locks in a prepare handler.

> > Initializing locks in child after fork without acquiring them before the
> > fork may result in corrupted program state and so is probably not a good
> > idea.
>
> Yes, but in practise, I think that this shouldn't be too much of a
> problem. Also note that you can very well have the same type of
> problem with sections not protected explicitely by locks: for example,
> if you have a thread working exclusively on an object (maybe part of a
> threadpool), a fork can very well happen while the object is in an
> inconsistent state. Acquiring locks before fork won't help that.

I think a worker thread that works exclusively on an object does not create the problem:
a) If the fork thread eventually needs to read the object then you need synchronization.
b) If the worker thread eventually writes data into file or DB then that operation will be completed at the parent process.

To summarize I think we should take the atfork path. An atfork handler does not need to acquire all locks, but only those required by library logic, which the handler is aware of, and as a bonus it can be used to do all sort of stuff such as cleaning up, reinitializing a library, etc...
History
Date User Action Args
2011-05-14 18:54:57niraisetrecipients: + nirai, gregory.p.smith, pitrou, vstinner, bobbyi, neologix, sdaoden
2011-05-14 18:54:57niraisetmessageid: <1305399297.63.0.0655124420366.issue6721@psf.upfronthosting.co.za>
2011-05-14 18:54:57nirailinkissue6721 messages
2011-05-14 18:54:56niraicreate