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.

classification
Title: add threading.RWLock
Type: enhancement Stage: patch review
Components: Library (Lib) Versions:
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Ofekmeister, Sebastian.Noack, asvetlov, christian.heimes, dan.oreilly, elarivie, jcea, jyasskin, kristjan.jonsson, mklauber, neologix, njs, pitrou, sbt, vrutsky
Priority: normal Keywords: needs review, patch

Created on 2010-05-24 09:55 by kristjan.jonsson, last changed 2022-04-11 14:57 by admin.

Files
File name Uploaded Description Edit
rwlock.patch kristjan.jonsson, 2010-05-24 09:55 review
Added-ShrdExclLock-to-threading-and-multiprocessing.patch Sebastian.Noack, 2012-09-30 02:01 review
sharablelock.patch kristjan.jonsson, 2012-09-30 17:19 review
Added-ShrdExclLock-to-threading-and-multiprocessing-2.patch Sebastian.Noack, 2012-09-30 18:40 review
rwlock.patch kristjan.jonsson, 2012-10-02 11:37 review
rwlock.patch kristjan.jonsson, 2012-10-04 10:29 review
rwlock-sbt.patch sbt, 2012-10-04 17:24 review
Messages (63)
msg106350 - (view) Author: Kristján Valur Jónsson (kristjan.jonsson) * (Python committer) Date: 2010-05-24 09:55
Provided is a patch, implementing a RWLock using a ConditionVariable and Lock.
It has rdlock() and wrlock() methods, and an unlock() method, similar to the rwlock API in pthreads.
It has context managers rdlocked() and wrlocked().
In addition, it conforms to the RLock interface, with acquire() and release() being aliases to wrlock() and unlock() respectively and when used as a context manager, it gets a wrlock()
There is no documentation yet, since this is just a proposal, but there are unit tests.

See also issue 8777 for another locking primitive, the Barrier.
msg106372 - (view) Author: Jeffrey Yasskin (jyasskin) * (Python committer) Date: 2010-05-24 16:39
This patch doesn't apply cleanly against the py3k tree. Since Python 2.7 is in beta, and there's no 2.8, this can only go into python 3, so you should work against that tree.

It's a bit annoying that the R in RWLock stands for a different word from in RLock. But I can't think of a better name.

Hm, the test for RWLock should use a Barrier to check for multiple readers.

I wonder if it makes sense to add a DeadlockError as a subclass of RuntimeError and throw that from trying to upgrade a read-lock to a write-lock.

The docs should describe why upgrading a read-lock to a write-lock is banned, or else people will try to add the feature.


test_wrrecursion should also check pure writer recursion. Oh, no, that's tested by RLockTests. Comment that please. The name of the test should probably mention write_then_read_recursion to distinguish it from write-then-write or read-then-write.

test_readers_writers doesn't quite test enough. (Feel free to add another test rather than changing this one.) You want to test that readers can't starve writers. For example, say we have:
  lock = RWLock()
  done = False
  def r():
    with lock.rdlocked():
      if done:
        return
      _wait()
  def w():
    nonlocal done
    with lock.wrlocked():
      done = True
  readers = Bunch(r, 50)
  _wait()
  writers = Bunch(w, 1)
  readers.wait_for_finished()
  writers.wait_for_finished()

In a naive implementation, there may never be a point where no reader has the lock held, and so the writer may never get in to tell them all to exit. I believe your implementation will already pass this test.

spelling: mathced

I'm not sure that "#threads will be few" is true for a read-lock, but I don't mind for the first implementation.

Generally put a space after # when it introduces a comment, start comments with a capital letter, and end them with punctuation so we know you didn't stop typing early.

In _rdlock could you flip the "not self.nw" condition? That way the else won't be a double-negative.

Can self.rcond.wait() ever throw an exception? I suspect you should use try:finally: to guard the "self.nr -= 1"

Why must the user hold a write lock to use Condition.wait? Semantically, a waiting on a read-lock would release the current thread's recursive read-locks, and all should be well.

It looks like self.owning holds one copy of each thread's id for each re-entry by that thread. That wasn't obvious so deserves a comment.

General API questions: 
 1. Given that the other locking APIs use "acquire" and "release" instead of "lock" and "unlock", perhaps this class should use "rdacquire" and "wracquire"?
 2. I wonder if it helps actual code to be able to just call "unlock" instead of "rdunlock" and "wrunlock". Code releasing the lock should always know which kind of lock it holds.

Otherwise, this looks basically correct. Thanks!
msg106373 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2010-05-24 16:40
I'm not sure it's a good idea to have a default context manager, and default acquire/release methods. "In the face of ambiguity, refuse the temptation to guess".

Is there any use in being able to use a RWLock inside a condition variable?

Docstrings should be polished (e.g. typo "must be mathced with an release"). I think wrlocked() and rdlocked() deserve a docstring too.

A style nit: generally, comments should be on their own line, before the code they comment. Also, there should be a space just after the "#" (see PEP 8 :-)).
msg106376 - (view) Author: Jeffrey Yasskin (jyasskin) * (Python committer) Date: 2010-05-24 17:43
In this case, "acquire" isn't ambiguous. All the other lock types actually acquire a write lock, so it makes sense to have the operation with the same name they use also acquire a write lock on this object.

I realized that read/write locks are actually shared/exclusive locks, which might form the basis for a name that doesn't collide with RLock. Boost (http://www.boost.org/doc/libs/1_43_0/doc/html/thread/synchronization.html#thread.synchronization.mutex_types.shared_mutex) uses shared_mutex for the concept, so SLock or SELock? There are some algorithms that write while the lock is acquired non-exclusive, so "shared" is actually a better name for the concept, even though posix and Java used read/write.

The possibility of lock downgrading (turning an exclusive lock into a shared lock, without allowing any other exclusive acquisitions in the mean time) might inform your decision about how to name "unlock".
msg106377 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2010-05-24 17:51
> In this case, "acquire" isn't ambiguous. All the other lock types
> actually acquire a write lock, so it makes sense to have the operation
> with the same name they use also acquire a write lock on this object.

Well, it looks like a strange kind of "consistency" to me, since other
lock types are not dual. Both posix and Java APIs don't seem to imply
that the write lock is the "default" lock in a RW lock. However, I'm not
a synchronization specialist.

> I realized that read/write locks are actually shared/exclusive locks,
> which might form the basis for a name that doesn't collide with RLock.
> Boost
> (http://www.boost.org/doc/libs/1_43_0/doc/html/thread/synchronization.html#thread.synchronization.mutex_types.shared_mutex) uses shared_mutex for the concept, so SLock or SELock?

SELock looks ok, but I have to say that RWLock is more obvious (I don't
need to do a search to guess that RW means "read/write").
msg106386 - (view) Author: Jeffrey Yasskin (jyasskin) * (Python committer) Date: 2010-05-24 20:44
You're definitely right that posix and java don't make these usable from the normal lock API. And I don't think it's essential that they be usable as RLocks, although it's nice for Conditions. I think what I'm actually saying is that, if you have an acquire() operation on RWLock, then it has to forward to the write lock. Forwarding to the read lock would have different semantics from RLock.acquire (2 threads could get in at once) and so would be incorrect.

I agree that RWLock is more obvious. As long as the docs mention that it's equivalent to a shared/exclusive lock, I don't care that much about the name. Just throwing out ideas.
msg168077 - (view) Author: Kristján Valur Jónsson (kristjan.jonsson) * (Python committer) Date: 2012-08-13 10:10
I should add that on Windows, the new SRW that is part of Vista and Windows 7, uses locking, that is it favors neither readers or writers.  It appears that nowadays the complex semantics of RWLocks have not really proven worthwile.  See http://msdn.microsoft.com/en-us/magazine/cc163405.aspx

Perhaps this proposed patch is overly complex.
msg168149 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-08-13 21:52
> Perhaps this proposed patch is overly complex.

I don't know. Do you think relaxing the semantics would make the implementation significantly faster?
(interesting link, btw)
msg171567 - (view) Author: Sebastian Noack (Sebastian.Noack) Date: 2012-09-29 12:38
I would love to see a reader/writer lock implementation shipped with Python's threading (and multiprocessing) module. But I have some issues with the patch:

1. I would avoid the terms 'read' and 'write' as those terms are referring just to one of many use cases. A better and more generic name would be shared and exclusive lock.

2. If we add a new synchronization primitive to the threading module we really should add it also to the multiprocessing module, for consistency and to keep switching between threading and multiprocessing as easy as it is right now.

3. The methods rdlock() and wrlock() might even block if you call them with blocking=False. That's because of they acquire the internal lock in a blocking fashion before they would return False.

4. As Antoine already pointed out, it is a bad idea to make acquiring the exclusive (write) lock, the default behavior. That clearly violates the Zen of Python, since explicit is better than implicit.

5. The issue above only raises from the idea that the RWLock should provide the same API as the Lock and RLock primitives. So everywhere where a lock primitive is expected, you can pass either a Lock, RLock or RWLock. That is actually a good idea, but in that case you should explicitly specify, whether to pass the shared (read) or the exclusive (write) lock.

Both issues 4. and 5. only raise from the idea that a shared/exclusive lock should be implemented as a single class. But having two different lock primitives, one for the shared lock and one for the exclusive lock and a function returning a pair of those, would be much more flexible, pythonic and compatible with existing lock primitives.

def ShrdExclLock()
  class _ShrdLock(object):
    def acquire(self, blocking=True):
      ...

    def release(self, blocking=True):
      ...

    def __enter__(self):
      self.acquire()
      retrun self

    def __exit__(self, exc_value, exc_type, tb):
      self.release()

  class _ExclLock(object):
    def acquire(self, blocking=True):
      ...

    def release(self, blocking=True):
      ...

    def __enter__(self):
      self.acquire()
      retrun self

    def __exit__(self, exc_value, exc_type, tb):
      self.release()

  return _ShrdLock(), _ExclLock()

# create a shared/exclusive lock
shrd_lock, excl_lock = ShrdExclLock()
msg171568 - (view) Author: Kristján Valur Jónsson (kristjan.jonsson) * (Python committer) Date: 2012-09-29 12:46
Excellent points.
For 3. however, I don't consider access to synchronized state to be "blocking".  Blocking means to block while waiting for the lock.  The internal lock is never held for any amount of time.
Perhaps I'll cook up a new patch with these thoughts.
msg171599 - (view) Author: Sebastian Noack (Sebastian.Noack) Date: 2012-09-29 23:46
Using a lock as context manager is the same as calling lock.acquire(blocking=True) and it will in fact block while waiting for an other thread to release the lock. In your code, the internal lock is indeed just hold for a very short period of time while acquiring or releasing a shared or exclusive lock, but it might add up to a notable amount of time dependent on how much concurrent threads are using the same RWLock and how slow/busy your computer is.

But what made me reconsider my point are following facts:

1. For example, when you acquire a shared (read) lock in non-blocking mode and False is returned, you assume that an other thread is holding an exclusive (write) lock. But that isn't necessarily the case, if it also returns False, when the internal lock is acquired by an other thread for example in order to acquire or release another shared (read) lock.

2. The internal lock must be acquired also in order to release a shared/exclusive lock. And the 'release' method (at least if implemented as for Lock and RLock) don't have a 'blocking' argument, anyway.

For that reasons, I think it is ok to block while waiting for the internal lock, even if the shared/exclusive lock was acquired in non-blocking mode. At least it seems to lead to less unexpected side effects, than returning False in case the internal lock is acquired.
msg171600 - (view) Author: Sebastian Noack (Sebastian.Noack) Date: 2012-09-30 02:01
I've added a new patch, that implements a shared/exclusive lock as described in my comments above, for the threading and multiprocessing module.
msg171626 - (view) Author: Kristján Valur Jónsson (kristjan.jonsson) * (Python committer) Date: 2012-09-30 11:02
Yes, any "blocking" on the internal lock is not semantic blocking due to the use of the Shared lock itself, but merely a technical aspect, not semantically visible to the program.  It is equivalent to the operating system pausing the thread, a techical detail opaque to the user program and somethign it cannot affect or influence.

"Blocking" in the sense of the lock is due to the semantic use of the lock itself and the resources that it protects.
msg171639 - (view) Author: Richard Oudkerk (sbt) * (Python committer) Date: 2012-09-30 14:00
> I've added a new patch, that implements a shared/exclusive lock as 
> described in my comments above, for the threading and multiprocessing 
> module.

The patch does not seem to touch the threading mode and does not come with tests.  I doubt the multiprocessing version is actually picklable since self._requirement is a lambda function.

Also it would be best to avoid making multiprocessing.synchronize depend on ctypes.  (See implementation of multiprocessing.Barrier or Issue #14953.)


Personally, I would prefer to make the shared and exclusive locks attributes of the same object, so one could do

   with selock.shared:
      ...

   with selock.exclusive:
      ...
msg171653 - (view) Author: Kristján Valur Jónsson (kristjan.jonsson) * (Python committer) Date: 2012-09-30 17:19
I can't say that I'm that familiar with multiprocessing to comment on that in particular.
But I do find your approach strange, to create two "lock-like" objects, in stead of the more familiar construct of having a "RWLock" (this is known from other languages) with two ways of claiming it.

Attached is a new patch.  I've adopted the name "SharableLock" perhaps it is bad.  It uses your idea of returning "lock-like" objects that do either shared or exclusive locking.

There are two implementations: SharableLock is much like the original patch, with two conditions and writer priority.
SimpleSharableLock drops all that, and uses a single condition and ad-hoc priority. I've added unittests that show that writer-starvation does not appear to be a problem.

Createing a Multiprocessing lock using the SharableLockBase should pose no difficulties.

The use of the SharableLock with Condition objects is forced to be Exclusive, since Condition objects typcially rely on their associated lock to synchronize internal state.
msg171659 - (view) Author: Sebastian Noack (Sebastian.Noack) Date: 2012-09-30 18:40
I was just waiting for a comment pointing out, that my patch comes without tests. :) Note that we are still discussing the implementation and this patch is just a proof of concept. And since the way it is implemented and the API it provides could still change, its quite pointless to write tests, until we at least agreed on the API.

I have uploaded a new patch. The way it is implemented now, is more like the Barrier is implemented. The common code is shared in the threading module and the shared/exclusive lock objects can be pickled now. I have also fixed a bug related to acquiring locks in non-blocking mode.

However the code still uses c_uint, but ctypes (and multiprocessing.sharedtypes) is only imported when ShrdExclLock is called. So it is just a lazy dependency, now. However the reason why I am using ctypes instead of python integers for threading and a BufferWrapper for multiprocessing (as the Barrier does) is, because of 2 of the 4 counters need to be continuously incremented, and c_uint has the nice feature that it can overflow, in contrast to python integers and integers in arrays. Also that way the implementation is simpler and it seems that there isn't much difference under the hood between using BufferWrapper() and RawValue().

A shared/exclusive lock isn't one lock but two locks, which are synchronized, but must be acquired separately. Similar to a pipe, which isn't one file, but one file connected to another file that reads whatever you have written into the first file. So it isn't strange to create two lock objects, as it also isn't strange that os.pipe() returns two file descriptors.

Also having a separate lock object for the shared and exclusive lock, each providing the same API (as Lock and RLock), gives you huge flexibility. You can acquire both locks using the with statement or pass them separately around. So for example when you have a function, thread or child process, that should only be able to acquire either the shared or the exclusive lock, you don't have to pass both locks. That also means that existing code that expects a lock-like object will be compatible with both the shared and exclusive lock.
msg171667 - (view) Author: Kristján Valur Jónsson (kristjan.jonsson) * (Python committer) Date: 2012-09-30 19:55
>A shared/exclusive lock isn't one lock but two locks, which are >synchronized, but must be acquired separately. Similar to a pipe, >which isn't one file, but one file connected to another file that >reads whatever you have written into the first file. So it isn't >strange to create two lock objects, as it also isn't strange that >os.pipe() returns two file descriptors

That's one way to look at it.  Another (and the more common) is to say that it is one lock, which can be in two different states.  The original patch is modeled on seveal such constructs that exist in the field, such as for example the SRW locks in windows: http://msdn.microsoft.com/en-us/library/windows/desktop/aa904937(v=vs.85).aspx

(In fact, I am rather leaning back towards the RWLock name again, since that seems to be the most commonly used on other platforms.  Oh, how tempting it is to bikeshed about such trivialities.)

Note that in my last patch, I provide the exclusive_locked() and shared_locked() methods that return exactly such locks as you describe.

I'd much rather have a single RW Lock object, with retgular locking proxies, rather than two distinct object, attached only by some shared common variables.

Also, with the structured approach I suggest, you can relatively easily create such a lock that also works across process boundaries by implementing a concrete class with SharableLockBase as a base.
msg171669 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-09-30 20:10
Ok, since we're bikeshedding, I like Richard's proposal best:

> Personally, I would prefer to make the shared and exclusive locks
> attributes of the same object, so one could do
> 
>    with selock.shared:
>       ...
> 
>    with selock.exclusive:
>       ...

Please note, the "same object" could simply be a namedtuple instance.

Also I think "shared/exclusive" indeed conveys the semantics better than "read/write".
msg171674 - (view) Author: Richard Oudkerk (sbt) * (Python committer) Date: 2012-09-30 21:00
@Sebastian: Both your patch sets are missing the changes to threading.py.
msg171693 - (view) Author: Sebastian Noack (Sebastian.Noack) Date: 2012-10-01 07:30
Yes, you could also look at the shared/exclusive lock as one lock with different states. But this approach is neither more common, have a look at Java's ReadWriteLock [1] for example, which works just like my patch does, except that a factory is returned instead of a tuple. Nor does it provide any of the benefits, I have mentioned before (same API as Lock and RLock, better compatibility with existing code an with statement, ability to pass the shared or exclusive lock separetly around). But maybe we could satisfy anybody, by following Richard's and Antoine's suggestion of returning a named tuple. So you could use the ShrdExclLock both ways:

# use a single object
lock = ShrdExclLock()

with lock.shared:
  ...

with lock.exclusive:
  ...

# unpack the the object into two variables and pass them separately around
shrd_lock, excl_lock = ShrdExclLock()

Thread(target=reader, args=(shrd_lock,)).start()
Thread(target=writer, args=(excl_lock,)).start)


The majority of us seems to prefer the terms shared and exclusive. However I can't deny that the terms read and write are more common, even though there are also notable exmples where the terms shared and exclusive are used [2] [3]. But let us ignore how other call it for now, and get to the origin of both set of terms, in order to figure out which fits best into Python:

shared/exclusive -> abstract description of what it is
read/write       -> best known use case

The reason why I prefer the terms shared and exculsive, is that it is more distinct and less likely to get misunderstand. Also naming a generic implementation after a specific use case is bad API design and I don't know any other case where that was done, in the Python core library.


[1] http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/locks/ReadWriteLock.html
[2] http://www.postgresql.org/docs/9.2/static/explicit-locking.html
[3] http://www.unix.com/man-page/freebsd/9/SX/
msg171695 - (view) Author: Sebastian Noack (Sebastian.Noack) Date: 2012-10-01 07:42
@richard: I'm sorry, but both of my patches contain changes to 'Lib/threading.py' and can be applied on top of Python 3.3.0. So can you explain what do you mean, by missing the changes to threading.py?
msg171696 - (view) Author: Kristján Valur Jónsson (kristjan.jonsson) * (Python committer) Date: 2012-10-01 09:41
> Personally, I would prefer to make the shared and exclusive locks
> attributes of the same object, so one could do
> 
>    with selock.shared:
>       ...
> 
>    with selock.exclusive:
>       ...
> Please note, the "same object" could simply be a namedtuple instance.
With this, you are stuck with employing a context manager model only.  You loose the flexibility to do explicit acquire_read() or acquire_write().
My latest patch has methods shared_lock(), exclusive_lock() that return proxy lock objects that can be used like context managers like you describe, but you still have the flexibility of using the lock manually.

As for the bikeshedding, let's look at the list of concrete implementations out there:
Windows: SRW locks (slim reader writer) http://msdn.microsoft.com/en-us/library/windows/desktop/aa904937(v=vs.85).aspx
Pthreads: rwlock_t (reader/writer) http://publib.boulder.ibm.com/iseries/v5r2/ic2924/index.htm?info/apis/users_86.htm
Java: ReadWriteLock, http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/ReadWriteLock.html

Am I missing anything?
Don't see why we need to adopt a completly different name or idiom to what people are used to.  Also, note that the java version is quite similar to Richard's proposal.
msg171697 - (view) Author: Kristján Valur Jónsson (kristjan.jonsson) * (Python committer) Date: 2012-10-01 09:49
>> shared/exclusive -> abstract description of what it is

If you want to argue it this way, I counter that the attributes "shared" and "exclusive" apply to the type of "access to the protected object" you are talking about, and yet, the name suggest that they are attributes of the lock itself.

In that sense, "reader lock" and "writer lock", describe attributes of the user of the lock, and the verbs "readlock" and "writelock" describe the operation being requested.

It's simply more difficult to use the more abstract concepts 'shared' and 'exclusive' as convenient and transparent descriptors of what the thing does, and likely to just brew confusion.
msg171698 - (view) Author: Richard Oudkerk (sbt) * (Python committer) Date: 2012-10-01 09:52
> @richard: I'm sorry, but both of my patches contain changes to 
> 'Lib/threading.py' and can be applied on top of Python 3.3.0. So can you 
> explain what do you mean, by missing the changes to threading.py?

I was reading the Rietveld review page

    http://bugs.python.org/review/8800/#ps6111

which only shows changes to multiprocessing/__init__.py and multiprocessing/synchronize.py.

The patch looks like it was produced using git rather than hg, so perhaps Rietveld got confused by this.  In that case it is a bug in Rietveld that it produced a partial review instead of producing no review.

> # unpack the the object into two variables and pass them separately around
> shrd_lock, excl_lock = ShrdExclLock()
> 
> Thread(target=reader, args=(shrd_lock,)).start()
> Thread(target=writer, args=(excl_lock,)).start)

Although using namedtuple is probably a good idea, I don't think it really adds much flexibility.  This example could just as easily be written

  selock = ShrdExclLock()

  Thread(target=reader, args=(selock.shared,)).start()
  Thread(target=writer, args=(selock.exclusive,)).start)
msg171699 - (view) Author: Richard Oudkerk (sbt) * (Python committer) Date: 2012-10-01 10:01
> With this, you are stuck with employing a context manager model only.  
> You loose the flexibility to do explicit acquire_read() or 
> acquire_write().

You are not restricted to the context manager model.  Just use selock.shared.acquire() or selock.exclusive.acquire().
msg171700 - (view) Author: Kristján Valur Jónsson (kristjan.jonsson) * (Python committer) Date: 2012-10-01 10:06
Perhaps I should have pointed out, for Sebastian's benefit, that my second patch uses "timeout" rather than "blocking" since that is the new black in python 3.
Also, I think the "threading" implementation shows clearly the problem of having two independent objects that are only losely bound by some shared common variables:  The threading.py version has to rely on c_types ints for the common counters.
If the two locks were merely two different views of a common RWLock object, this problem could go away, and you could have the threading.RWLock and the multiprocessing.RWLock be different concrete classes derived from a common base class.

I also think it is time to drop the "writer preference" model, since it just adds complexity with doubtful benefits.  Sebastian's model also does that.

I'll provide a new example patch presently.
msg171703 - (view) Author: Sebastian Noack (Sebastian.Noack) Date: 2012-10-01 11:22
> If you want to argue it this way, I counter that the attributes
> "shared" and "exclusive" apply to the type of "access to the
> protected object" you are talking about, and yet, the name suggest
> that they are attributes of the lock itself.

A lock's sole purpose is to synchronize access to a protected object or context. So naming a lock after its type of protection absolutely makes sense. Those names are also not supposed to be attributes of the lock, rather two locks (a shared and an exclusive lock) should be created, that might be returned as a namedtuple for convenience.

> In that sense, "reader lock" and "writer lock", describe attributes
> of the user of the lock, and the verbs "readlock" and "writelock"
> describe the operation being requested.

The user of the lock isn't necessarily a reader or writer. This is just one of many possible use cases. For example in a server application a shared/exclusive lock might be used to protect a connection to the client. So every time a thread wants to use the connection, a shared lock must be acquired and when a thread wants to shutdown the connection, the exclusive lock must be acquired, in order to ensure that it doesn't interrupt any thread still processing a request for that connection. In that case you clearly wouldn't call the users reader and writer.


> The patch looks like it was produced using git rather than hg, so
> perhaps Rietveld got confused by this.  In that case it is a bug
> in Rietveld that it produced a partial review instead of producing
> no review.

Yes, I have imported the Python 3.3.0 tree into a local git repository and created the patch that way. Since patches generated with git are still compatible with the 'patch' program in order to apply them, I hope that isn't a problem.


> Although using namedtuple is probably a good idea, I don't think it
> really adds much flexibility.  This example could just as easily be
> written
>
>  selock = ShrdExclLock()
>
>  Thread(target=reader, args=(selock.shared,)).start()
>  Thread(target=writer, args=(selock.exclusive,)).start()

Yes, that is true, but in some cases it is more convenient to be able unpack the shared/exclusive lock into two variables, with a one-liner. And defining a namedtuple doesn't require any extra code compared to defining a class that holds both locks. In fact it needs less code to be implemented.

However the flexibility comes from having two lock objects, doesn't matter how they are accessed, instead as suggested by Kristján to have a single lock object, which just provides proxies for use with the with statement.


> I also think it is time to drop the "writer preference" model, since
> it just adds complexity with doubtful benefits.  Sebastian's model
> also does that.

I have implemented the simplest possible acquisition order. The lock acquired first will be granted first. Without that (or a more advanced policy) in applications with concurrent threads/processes that are heavily using the shared lock, the exclusive lock can never be acquired, because of there is always a shared lock acquired and before it is released the next shared lock will be acquired.
msg171708 - (view) Author: Richard Oudkerk (sbt) * (Python committer) Date: 2012-10-01 12:16
I think Sebastian's algorithm does not correctly deal with the non-blocking case.  Consider the following situation:

* Thread-1 successfully acquires exclusive lock.
  Now num_got_lock == 1.

* Thread-2 blocks waiting for shared lock.
  Will block until (num_got_lock == 1 and excl_count == 0).
  Now num_got_lock == 1.

* Thread-3 does non-blocking acquire of shared lock but fails.
  Now num_got_lock == 2.

Now, since num_got_lock == 2, the predicate that Thread-2 is waiting for will not happen until num_got_lock overflows.

This is probably fixable if we just prevent a failed non-blocking acquire from modifying num_acq_lock and num_got_lock.  (But I don't see how to extend the algorithm to allow timeouts.)
msg171709 - (view) Author: Richard Oudkerk (sbt) * (Python committer) Date: 2012-10-01 12:32
My previous comment applied to Sebastian's first patch.  The second seems to fix the issue.
msg171710 - (view) Author: Christian Heimes (christian.heimes) * (Python committer) Date: 2012-10-01 12:35
I wonder, why are you creating your own algorithm here? There must be plenty of reference implementations that are already used in production code. Don't be a shamed to copy a Java implementation! :) The entire threading module is a rip-off of the Java threading API.
msg171713 - (view) Author: Sebastian Noack (Sebastian.Noack) Date: 2012-10-01 13:18
I would love to see how other people would implement a shared/exclusive lock that can be acquired from different processes. However it really seems that nobody did it before. If you know a reference implementation I would be more than happy.

There are plenty of implementations for threading only, but they won't work with multiprocessing, due to the limitations in the ways you can share data between processes.
msg171714 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-10-01 13:21
On the naming front: shorthands like "Shrd" and "Excl" are a bit frown upon. Since "SharedExclusiveLock" is on the long side, I would suggest calling the API "SELock".
msg171716 - (view) Author: Christian Heimes (christian.heimes) * (Python committer) Date: 2012-10-01 13:39
A RW lock is part of POSIX threads [1]. It's usually a good idea to either use POSIX functions or to mimic their behavior. After all POSIX is an industry standard.

Boost and Java have several lock and rw lock implementations. Wikipedia [2] is a good starting point for the various implementations. The page also mentions a seqlock which looks interesting to me as it's fast for few writers with lots of readers.

[1] http://linux.die.net/man/3/pthread_rwlock_init 
[2] http://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock
msg171717 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-10-01 13:46
> A RW lock is part of POSIX threads [1]. It's usually a good idea to
> either use POSIX functions or to mimic their behavior. After all POSIX
> is an industry standard.

We've already departed from that. Our Lock is nothing like a mutex, for
example (it's more of a binary semaphore).
We follow POSIX when exposing POSIX APIs (as in the os module), but
otherwise we have our own abstractions, for example the 3.x I/O stack.
msg171718 - (view) Author: Sebastian Noack (Sebastian.Noack) Date: 2012-10-01 13:51
Thanks, but as I already said there are a lot of implementations for shared/exclusive lock that can be acquired from different threads. But we need with threading as well as with multiprocessing.

And by the way POSIX is the standard for implementing UNIX-like systems and not an industry standard for implementing anything, including high-level languages like Python.
msg171721 - (view) Author: Charles-François Natali (neologix) * (Python committer) Date: 2012-10-01 14:44
> The page also mentions a seqlock which looks interesting to me as
> it's fast for few writers with lots of readers.

A seqlock is suitable for consistent views of simple data structures (e.g. a counter in the Linux kernel), but it won't fly for a high-level language like Python.
The problem is that, despite its name, it's not a lock, but it's based on retries, which means that:
- the critical section must be idempotent (no side effect like incrementing a variable, or crediting a bank account :-)
- your critical section is simple enough so that it can tolerate inconsistent views, e.g.:

with seqlock.rlock():
    z = 1/(x-y)

if the writer threads make sure that x!=y when they hold the seqlock, you can still, if you're unlucky, fall at the wrong time and x==y, then you get a nice ZeroDivisionError.

(And yes, you have the same kind of issues with transational memory, as well as others...).


Otherwise, having a rwlock would be a nice addition, but since the GIL serializes everything anyway, this isn't likely to benefit many situations (unless you do I/O, of course), on CPython at least.
That's why it's definitely important to have the equivalent for multiprocessing.

Also, I prefer reader-writer lock because that's how everyone calls it (not only POSIX), and RWLock looks better than SELock (well, at least to me).
msg171780 - (view) Author: Kristján Valur Jónsson (kristjan.jonsson) * (Python committer) Date: 2012-10-02 09:51
> You are not restricted to the context manager model.  Just use selock.shared.acquire() or selock.exclusive.acquire().

The unlock operation is the same, so now you have to arbitrarily pick one of the "lockd" and chose release().  Why take a construct which is essentially a lock that can be acquired in two different ways and force people to view it as separate objects?

I much prefer a simple RWLock primitve, such as is popular in other programming environments, and add your convenient pseudo-locks on top.
That way, we are not forcing a certain myopic view of what an RWLock is down people's throat.
msg171781 - (view) Author: Kristján Valur Jónsson (kristjan.jonsson) * (Python committer) Date: 2012-10-02 09:58
> We've already departed from that. Our Lock is nothing like a mutex, for
> example (it's more of a binary semaphore).

This is not by nature of good design, but an accident.  C python needed both mutex and signaling ability and decided that a single non-recursive lock were good enough for that.  This is a debatable choice since all modern systems consider these two different needs and provide different primitives to satisfy them.  The "Lock" was then exposed to Python and RLock grafted on top to fix the non-recursiveness problem.  Then we added signaling in the form of Events, Semaphores and Condition variables.
Had this ben more purposefully designed, then there would be no Lock or RLock in threading.py, only a Mutex.
msg171782 - (view) Author: Kristján Valur Jónsson (kristjan.jonsson) * (Python committer) Date: 2012-10-02 10:21
> I have implemented the simplest possible acquisition order.
> The lock acquired first will be granted first. Without that (or a more
> advanced policy) in applications with concurrent threads/processes
> that are heavily using the shared lock, the exclusive lock can never
> be acquired, because of there is always a shared lock acquired and
> before it is released the next shared lock will be acquired.

I think you got that argument backwards.  The simple greedy policy you implement works well provided there are not too many readers. Otherwise, the writers will be starved, since they have to wait for an oppertune moment when no readers are active to get a foot in the door, so to speak.
Your approach is similar to my "SimpleSharableLock" from my second patch in that respect, and also to Microsoft's SRW Locks (http://msdn.microsoft.com/en-us/magazine/cc163405.aspx).  They specifically state:

" This means that if your application requires that data updates take priority over data reads, you might want to consider a different reader/writer lock that favors writers".

While the test_threading.py showed ok results with the simple approach, my preliminary tests on multiprocessing show that writers need to be given priority if they are not to be starved.
msg171783 - (view) Author: Richard Oudkerk (sbt) * (Python committer) Date: 2012-10-02 10:44
> I think you got that argument backwards.  The simple greedy policy you 
> implement works well provided there are not too many readers. Otherwise, 
> the writers will be starved, since they have to wait for an oppertune 
> moment when no readers are active to get a foot in the door, so to speak.

Actually, I think Sebastian's algorithm attempts to be fair to both readers and writers.

If there is a writer waiting then "self._wait_count > self._granted_count".  The writer cannot be prempted by a later reader because the reader would find "waitno > self._granted_count" until after writer has been granted the lock.
msg171785 - (view) Author: Richard Oudkerk (sbt) * (Python committer) Date: 2012-10-02 10:59
> The unlock operation is the same, so now you have to arbitrarily pick one 
> of the "lockd" and chose release().

That depends on the implementation.  In the three implementations on

    http://en.wikipedia.org/wiki/Readers-writers_problem

the unlock operateration is different for readers and writers.

> Why take a construct which is essentially a lock that can be acquired in two 
> different ways and force people to view it as separate objects?

I don't see why writing

    lock.exclusive.acquire()

really requires a different way of thinking compared to writing

    lock.exclusive_acquire()

or

    lock.acquire_exclusive()
msg171786 - (view) Author: Kristján Valur Jónsson (kristjan.jonsson) * (Python committer) Date: 2012-10-02 11:37
Here is a new patch.  it is complete with:
threading implementation and tests
multiprocessing implementation and tests.

Let's leave the naming bikeshedding a bit and focus on some practical aspects:

1) The threading version contains a RWLock and a FairRWLock.  Both support recursion, although not upgrading from read to write access.  This is achieved with a list of 'owning' threads.
2) the "Fair" version provides 'write' priority, blocking further first acquisitions in read mode until no writers are waiting.

Multiprocessing:  Because there is no way I know to share a list of owning thread ids, this version is more limited:
a) The RWLock() only contains one owing thread ID.  This allows recursion but some error checking present in the threading version cannot be implemented.
b) the FairRWLock() again provides writer priority.  But to do that, we have to allow 'recursive' read if a writer is waiting, if we allow recursion at all.  And for that, we need to know what threds own the lock in read mode.  Since we don't have that information, this version of the lock disallows recursion alltogether.

Discussion:
Recursion/No recursion?  The Windows SRW locks disallow recursion.  Their rationale is here:  http://msdn.microsoft.com/en-us/magazine/cc163405.aspx: 
"First, regarding recursive acquires: if the locking policy you’ve designed for your application requires that synchronization objects be acquired recursively, this is very possibly a red flag telling you to re-examine your locking policy to eliminate the recursion. This is our opinion and results from the additional overhead of executing the lock acquisition and release code multiple times and, perhaps more importantly, because ensuring that the balance of lock releases with the lock acquisitions is often difficult to prove correct."

Of course the SRWLock is a "slim" lock and thus can be forgiven for not providing such functionality.

"Fair" vs "Non-fair" (Fair is not a good term, writer priority would be better).  The reason I'm coming back to this being useful is tests using the multiprocessing module.  The tests there show without doubt that a simple greedy locking algorithm fails miserably if there are more readers than writers.  The test "test_writer_success" has been adorned with a timer, and the simple version completes in 15 seconds, while the "fair" version completes in 0.5 seconds.

Good times!
msg171788 - (view) Author: Kristján Valur Jónsson (kristjan.jonsson) * (Python committer) Date: 2012-10-02 11:42
Oh, I forgot to mention:  Once one gets into the domain of allowing such niceties as writer priority, surely you can agree that the implementation of both locking modes belongs in the same class instance.  That is just plain good coding practice, allowing for class invariants, state assertions and such things.
Two class instances coupled by a bunch of common variables is IMHO neither good style nor practice.
msg171789 - (view) Author: Sebastian Noack (Sebastian.Noack) Date: 2012-10-02 11:53
Exactly, with my implemantation "the lock acquired first will be granted first". There is no way that either shared nor exclusive locks can starve, and therefore it should satisfy all use cases. Since you can only share simple datastructures like integers across processes, I also found that this seems to be the only policy (except ignoring the acquisition order at all), that can be implemented for multiprocessing.

I have also looked at the seqlock algorithm, which seems to be great for use cases where the exclusive lock is acquired rather rarely and where your "reader" code is in fact read-only and therefore can be repeated. But in any other case a seqlock would break your code. However the algorithm is ultra simple and can't be implemented as lock-like object anyway. Though you could implement it as context manager, but that would hide the fact that the "reader" code will be repeated. So if you find yourself that a seqlock is that what you need for your specific use case, you can just use the algorithm like below:

lock = multiprocessing.Value(0)
count = multiprocessing.Value(0)

def do_read():
  while True:
    if count.value % 2:
      continue
    data = ...
    if count.value % 2:
      continue
    return data

def do_write(data):
  with lock:
    count.value += 1
    # write data
    count.value += 1

I have also experimented with implementing a shared/exclusive lock on top of a pipe and UNIX file locks (https://gist.github.com/3818148). However it works only on Unix and only with processes (not threads). Also it turned out that UNIX file locks don't implement an acquisition order. So exclusive locks can starve, which renders it useless for most use cases.
msg171790 - (view) Author: Sebastian Noack (Sebastian.Noack) Date: 2012-10-02 12:03
@Kristján: Uhh, that is a huge amount of code, more than twice as much (don't counting tests) as my implementation, to accomplish the same. And it seems that there is not much code shared between the threading and multiprocessing implementation. And for what? Ah right, to make the API suck as much as the Windows API does. Please tell me more about good coding practice. ;)
msg171792 - (view) Author: Kristján Valur Jónsson (kristjan.jonsson) * (Python committer) Date: 2012-10-02 12:09
This amount of code provides recursion, context managers, condition variable compatibility, timeout functionality, error checking and conformance with the unit tests.

The actual locking code is encapsulated in the three functions acquire_read(), acquire_write, release().

The requirements and possibilities between threading and multiprocessing are many and multiple.  Sharing the implementation has the drawback of imposing the shortcomings and preformance bottlenecks of the multiprocessing implementation on the threading implementation, for no good reason.

I't tell you about good programming practice, but I'm too busy trying to understand the actual locking policy in your patch.  Sometimes comments in code can be helpful.
msg171793 - (view) Author: Kristján Valur Jónsson (kristjan.jonsson) * (Python committer) Date: 2012-10-02 12:22
Ah, you are implementing an FIFO lock.  That should have been made clear.
I see it now that you grant the lock in the order that the acquisition is attempted.
Ok, this is fine, but with one important caveat:  Explicit handoff such as that can suffer from "lock convoying".  In contested situation this can result in the protected resource being much less available than it ought to be.

In my dayjob, I write locking primitives for Stackless Python.  We used to employ handoff until we found out that this caused performance problems.  All the locking primitives now used by Eve Online and in Stacklesslib are 'greedy' and don't attempt fairness.  This has resulted in much improvement in resource usage.

For this reason, I explicitly did not build any such mechanism into my RWLock implementation.  Any thread coming in can claim the lock, provided the policy (writer priority policy) doesn't kick in.
In those rare cases where a special locking policy such as FIFO fairness is _required_ it is always possible to construct such a thing in python using e.g. a queue of condition variables.

For information on this, please see the following resource:
http://www.bluebytesoftware.com/blog/PermaLink,guid,e40c2675-43a3-410f-8f85-616ef7b031aa.aspx
msg171877 - (view) Author: Kristján Valur Jónsson (kristjan.jonsson) * (Python committer) Date: 2012-10-03 10:44
I admit that I kind of like Java's approach to this.  First off, they define an interface, ReadWriteLock:
http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/ReadWriteLock.html
There, they also discuss the different choices an implementation of the interface can make, regarding a) policy, b) reentrancy, c) upgrading or downgrading.
A concrete implemention is then presented in the form of the ReentrantReadWriteLock, with documented behaviour for the above.
The rest of threading is also, as previously pointed out, more or less a rip-off from Java.

Since there is no single "correct" choice for the above, and since the implementation restrictions are different between inter-process and inter-thread locks, it would make sense to adopt a similar model, where a RWLock() function is a factory function, taking an argument specify a desired class of locks.

The policies that have been seen in this thread are:
a) greedy policy (no policy)
b) writer preference
c) 'Fair' (or in-order) preference.
All have their benefits and disadvantages.

We have also seen "recursive" and "nonrecursive".

The restrictions appear more serious in the inter-process case since I don't know if it is possible to maintain a shared dynamic array of thread ids across processes.
msg171883 - (view) Author: Richard Oudkerk (sbt) * (Python committer) Date: 2012-10-03 13:43
> Multiprocessing:  Because there is no way I know to share a list of 
> owning thread ids, this version is more limited

Why do you need a *shared* list?  I think it should be fine to use a per-process list of owning thread ids.   So the current thread owns the lock if and only if it is in the current process's list of owners.

(On Unix you should probably clear the list when you fork by using multiprocessing.util.register_after_fork() in the initializer.)
msg171891 - (view) Author: Kristján Valur Jónsson (kristjan.jonsson) * (Python committer) Date: 2012-10-03 14:54
Excellent point, I hadn't thought of that!
Yes, it is is sufficient to test if _I_ am in the list.  I'll make the necessary changes. That will make the thread/process implementation virtually identical.
msg171914 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-10-04 01:02
I don't like the API. Why should I write "rwlock.acquire_read()" instead of "rwlock.reader.acquire()"? There should be one obvious way to do it.

The fact that "reader_lock" and "writer_lock" return a new object every time is a bit suboptimal, IMO.

Also, RWLockBase should be private (_RWLockBase).

Ah, and the patch needs docs :)
msg171915 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-10-04 01:04
By the way, the fact that acquire_read() is documented as "Acquire the lock in shared mode" and acquire_write() as "Acquire the lock in exclusive mode" hints that perhaps RWLock is not the right name ;)
msg171930 - (view) Author: Kristján Valur Jónsson (kristjan.jonsson) * (Python committer) Date: 2012-10-04 10:29
Ok, here is a new patch.  It takes into account various comments and suggestions:
1) The interface is java-like. a single RWLock instance only has attributes reader_lock and writer_lock.
2) Since the "owning" array needs only be process local, the same semantics can be used for multiprocessing.  Multiprocessing version only does minimal patching of the threading classes, similarly to Barrier.  There is implementation coupling between threading and multiprocessing but that is probably ok.
3) Test are there, except that the final tests in the multiprocessing, those that used to rely on data gathered in lists, are nerfed.  Not sure how to gather data when multiprocessing
4) I also didn't get the "manager" tests to work.  I don't know what managers are or what they are supposed to do, and why they use proxy objects.  I attempted to create a proxy object in managers.py but it failed the tests.  Also, I am unsure if it is sufficient to proxy only the RWLock object, or if the _ReaderLock and _WriterLock instances that are returned should be proxied too.

Documentation i stole from Java.
The locking policy is the same as the RecursiveReaderWiterLock from java, i.e. readers wait for writers, but otherwise no preference.  Recursion is provided for readers and writers, but not upgrade/downgrade.

Again, this is not intended to be final code, particularly I would like help and suggestions for the multiprocessing tests and the manager code.

Also, while I strongly prefer RWLock, or ReaderWriterLock, or something similar, basically because it fits with our already java-ized threading module and the terminology from everywhere else in the field today, I'm not going to be enormously difficult about it.

cheers!
msg171975 - (view) Author: Richard Oudkerk (sbt) * (Python committer) Date: 2012-10-04 16:55
Attached is a new version of Kristjan's patch with support for managers.  (A threading._RWLockCore object is proxied and wrapped in a local instance of a subclass of threading.RWLock.)

Also I made multiprocessing.RWLock.__init__() use multiprocessing.util.register_after_fork() to clear self._owners.  Otherwise on Unix you can run into trouble if a forked processes starts a new thread whose id was in self._owners at the time of the fork.

I found that test_many_readers() and test_recursion() tended to fail when run with processes (on Windows).  This is because starting a process on Windows is slow, particularly with a debug build.  (Some of the buildbots running Windows in a VM can be crazily slow.)  Each reader only held the lock for 0.02 secs which is much less than the time to start a process on Windows.  This meant that it was easy to never have overlapping ownership, causing the tests to fail.

I fixed this by starting the readers while holding an exclusive, waiting for period, and then releasing the exclusive lock.  This makes it possible to change

    self.assertTrue(max(nlocked) > 1)

to

    self.assertEqual(max(nlocked), N)

Choosing timeouts to keep the buildbots happy can be a pain:-(
msg171979 - (view) Author: Richard Oudkerk (sbt) * (Python committer) Date: 2012-10-04 17:24
Fixed patch because I didn't test on Unix...
msg172064 - (view) Author: Kristján Valur Jónsson (kristjan.jonsson) * (Python committer) Date: 2012-10-05 09:51
Thanks Richard.
I wonder if the issues with the multiprocessing tests can be fixed by making use of Barriers?

One of the reasons I introduced Barriers into the lib originally was my alarm at seeing the various springklings of different _wait() calls in the unittests, particularly in the threading module (there are Condition variable tests there still that have race conditions, that are cunnrently hidden by liberal use of _wait())

Then I started to wonder if it were appropriate to use a Barrier in the Condition variable tests, particularly given that the former is implemented by way of the latter :)
msg172071 - (view) Author: Richard Oudkerk (sbt) * (Python committer) Date: 2012-10-05 10:50
Kristjan: you seem to have attached socketserver.patch to the wrong issue.
msg172074 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-10-05 11:52
Le vendredi 05 octobre 2012 à 09:51 +0000, Kristján Valur Jónsson a
écrit :
> Then I started to wonder if it were appropriate to use a Barrier in
> the Condition variable tests, particularly given that the former is
> implemented by way of the latter :)

Indeed, the reason I wrote the lock tests that way is that I want the
tests to be independent of the primitives under test. Otherwise things
become very messy.
msg274765 - (view) Author: Ofek Lev (Ofekmeister) * Date: 2016-09-07 06:02
What is the status of the patch?
msg274795 - (view) Author: Kristján Valur Jónsson (kristjan.jonsson) * (Python committer) Date: 2016-09-07 11:10
Seems to have fizzled out due to the intense amount of bikeshedding required.
msg287745 - (view) Author: Nathaniel Smith (njs) * (Python committer) Date: 2017-02-14 09:13
Side note, in case anyone else finds themselves looking for the MSDN magazine article referenced above: it appears to be in the June 2007 issue, titled "CONCURRENCY: Synchronization Primitives New To Windows Vista". 

For the moment at least, this link seems to work: http://download.microsoft.com/download/3/A/7/3A7FA450-1F33-41F7-9E6D-3AA95B5A6AEA/MSDNMagazineJune2007en-us.chm
msg360026 - (view) Author: Éric Larivière (elarivie) Date: 2020-01-15 06:09
Hi,

Sorry to wake up a 10 years old discussion


But I think that you might be interested in the following Python package that I created and maintain since few years now:

    https://pypi.org/project/readerwriterlock/


1- It implements the three type of reader writer lock:
  - Read Priority
  - Write Priority
  - Fair Priority

2- It matches the interface of python threading.Lock

  More specifically:
    def acquire(self, blocking: bool = True, timeout: float = -1) -> bool:
    def release(self) -> None:

As you can see it supports the 'blocking' and the 'timeout' parameters) and it uses the same methods name

3- It supports context manager (__enter__, __exit__)

4- It is also possible (currently not well documented) to provide a lock factory when initializing a new reader writer lock to specify the internal lock mechanism to use (by default it uses threading.Lock).

    def __init__(self, lock_factory: Callable[[], Lockable] = lambda: threading.Lock()) -> None:

This hidden feature allows to offer the possibility to implement your own lock mechanism (using port, file on disk, etc, ...) and the reader writer lock will internally use it (This open the door for multiprocessing locking)
msg360031 - (view) Author: Andrew Svetlov (asvetlov) * (Python committer) Date: 2020-01-15 09:20
Thanks for sharing, your project looks viable!
History
Date User Action Args
2022-04-11 14:57:01adminsetgithub: 53046
2020-01-15 09:20:07asvetlovsetmessages: + msg360031
2020-01-15 06:09:57elariviesetnosy: + elarivie

messages: + msg360026
versions: - Python 3.4
2017-12-27 20:55:38jceasetnosy: + jcea
2017-02-14 09:13:01njssetnosy: + njs
messages: + msg287745
2016-09-07 11:10:56kristjan.jonssonsetmessages: + msg274795
2016-09-07 06:02:23Ofekmeistersetnosy: + Ofekmeister
messages: + msg274765
2015-03-25 17:31:41dan.oreillysetnosy: + dan.oreilly
2013-02-22 15:28:55vrutskysetnosy: + vrutsky
2012-10-05 11:52:45pitrousetmessages: + msg172074
2012-10-05 10:54:33kristjan.jonssonsetmessages: - msg172065
2012-10-05 10:53:59kristjan.jonssonsetfiles: - socketserver.patch
2012-10-05 10:50:00sbtsetmessages: + msg172071
2012-10-05 10:23:54kristjan.jonssonsetfiles: + socketserver.patch

messages: + msg172065
2012-10-05 09:51:42kristjan.jonssonsetmessages: + msg172064
2012-10-04 17:24:34sbtsetfiles: - rwlock-sbt.patch
2012-10-04 17:24:23sbtsetfiles: + rwlock-sbt.patch

messages: + msg171979
2012-10-04 16:55:59sbtsetfiles: + rwlock-sbt.patch

messages: + msg171975
2012-10-04 10:29:47kristjan.jonssonsetfiles: + rwlock.patch

messages: + msg171930
2012-10-04 01:04:18pitrousetmessages: + msg171915
2012-10-04 01:02:48pitrousetmessages: + msg171914
2012-10-03 14:54:33kristjan.jonssonsetmessages: + msg171891
2012-10-03 13:43:26sbtsetmessages: + msg171883
2012-10-03 10:44:33kristjan.jonssonsetmessages: + msg171877
2012-10-02 12:22:46kristjan.jonssonsetmessages: + msg171793
2012-10-02 12:09:50kristjan.jonssonsetmessages: + msg171792
2012-10-02 12:03:24Sebastian.Noacksetmessages: + msg171790
2012-10-02 11:53:08Sebastian.Noacksetmessages: + msg171789
2012-10-02 11:42:23kristjan.jonssonsetmessages: + msg171788
2012-10-02 11:37:51kristjan.jonssonsetfiles: + rwlock.patch

messages: + msg171786
2012-10-02 10:59:55sbtsetmessages: + msg171785
2012-10-02 10:44:19sbtsetmessages: + msg171783
2012-10-02 10:21:26kristjan.jonssonsetmessages: + msg171782
2012-10-02 09:58:18kristjan.jonssonsetmessages: + msg171781
2012-10-02 09:51:20kristjan.jonssonsetmessages: + msg171780
2012-10-01 14:44:06neologixsetnosy: + neologix
messages: + msg171721
2012-10-01 13:51:08Sebastian.Noacksetmessages: + msg171718
2012-10-01 13:46:54pitrousetmessages: + msg171717
2012-10-01 13:39:54christian.heimessetmessages: + msg171716
2012-10-01 13:21:59pitrousetmessages: + msg171714
2012-10-01 13:18:14Sebastian.Noacksetmessages: + msg171713
2012-10-01 12:35:30christian.heimessetnosy: + christian.heimes
messages: + msg171710
2012-10-01 12:32:24sbtsetmessages: + msg171709
2012-10-01 12:16:35sbtsetmessages: + msg171708
2012-10-01 11:22:14Sebastian.Noacksetmessages: + msg171703
2012-10-01 10:06:28kristjan.jonssonsetmessages: + msg171700
2012-10-01 10:01:31sbtsetmessages: + msg171699
2012-10-01 09:52:23sbtsetmessages: + msg171698
2012-10-01 09:49:49kristjan.jonssonsetmessages: + msg171697
2012-10-01 09:41:16kristjan.jonssonsetmessages: + msg171696
2012-10-01 07:42:09Sebastian.Noacksetmessages: + msg171695
2012-10-01 07:30:20Sebastian.Noacksetmessages: + msg171693
2012-09-30 21:00:32sbtsetmessages: + msg171674
2012-09-30 20:10:50pitrousetmessages: + msg171669
2012-09-30 19:55:19kristjan.jonssonsetmessages: + msg171667
2012-09-30 18:40:17Sebastian.Noacksetfiles: + Added-ShrdExclLock-to-threading-and-multiprocessing-2.patch

messages: + msg171659
2012-09-30 17:19:07kristjan.jonssonsetfiles: + sharablelock.patch

messages: + msg171653
2012-09-30 14:00:26sbtsetnosy: + sbt
messages: + msg171639
2012-09-30 11:02:39kristjan.jonssonsetmessages: + msg171626
2012-09-30 02:01:42Sebastian.Noacksetfiles: + Added-ShrdExclLock-to-threading-and-multiprocessing.patch

messages: + msg171600
2012-09-29 23:46:52Sebastian.Noacksetmessages: + msg171599
2012-09-29 12:56:40christian.heimessetversions: + Python 3.4, - Python 3.2
2012-09-29 12:46:22kristjan.jonssonsetmessages: + msg171568
2012-09-29 12:38:58Sebastian.Noacksetnosy: + Sebastian.Noack
messages: + msg171567
2012-08-13 21:52:09pitrousetmessages: + msg168149
2012-08-13 10:10:25kristjan.jonssonsetmessages: + msg168077
2012-08-08 21:30:06asvetlovsetnosy: + asvetlov
2012-08-07 15:40:04mklaubersetnosy: + mklauber
2010-05-24 20:44:35jyasskinsetkeywords: patch, patch, needs review

messages: + msg106386
2010-05-24 17:51:11pitrousetmessages: + msg106377
2010-05-24 17:43:38jyasskinsetkeywords: patch, patch, needs review

messages: + msg106376
2010-05-24 16:40:05pitrousetkeywords: patch, patch, needs review
nosy: + pitrou
messages: + msg106373

2010-05-24 16:39:11jyasskinsetkeywords: patch, patch, needs review

messages: + msg106372
2010-05-24 11:41:07pitrousetkeywords: patch, patch, needs review
nosy: + jyasskin
stage: patch review

versions: + Python 3.2, - Python 2.7
2010-05-24 09:55:40kristjan.jonssoncreate