diff -r 5064a5c5b1a3 Lib/test/lock_tests.py --- a/Lib/test/lock_tests.py Mon Apr 13 23:17:20 2015 -0400 +++ b/Lib/test/lock_tests.py Tue Apr 14 15:40:49 2015 -0500 @@ -149,7 +149,6 @@ Tests for non-recursive, weak locks (which can be acquired and released from different threads). """ - @unittest.skipIf(support.is_jython, "Jython only supports recursive locks") def test_reacquire(self): # Lock needs to be released before re-acquiring. lock = self.locktype() @@ -169,7 +168,6 @@ _wait() self.assertEqual(len(phase), 2) - @unittest.skipIf(support.is_jython, "Java does not allow locks to be released from different threads") def test_different_thread(self): # Lock can be released from a different thread. lock = self.locktype() @@ -291,10 +289,9 @@ results2.append((r, t2 - t1)) Bunch(f, N).wait_for_finished() self.assertEqual(results1, [False] * N) - epsilon = 1e-5 # wait time is hard to test precisely, so keep low resolution for r, dt in results2: self.assertFalse(r) - self.assertTrue(dt >= (0.2 - epsilon), dt) + self.assertTrue(dt >= 0.2, dt) # The event is set results1 = [] results2 = [] @@ -321,13 +318,13 @@ lock = threading.Lock() cond = self.condtype(lock) cond.acquire() - self.assertTrue(lock.acquire(False)) # All locks in Jython are recursive! + self.assertFalse(lock.acquire(False)) cond.release() self.assertTrue(lock.acquire(False)) - self.assertTrue(cond.acquire(False)) # All locks in Jython are recursive! + self.assertFalse(cond.acquire(False)) lock.release() with cond: - self.assertTrue(lock.acquire(False)) # All locks in Jython are recursive! + self.assertFalse(lock.acquire(False)) def test_unacquired_wait(self): cond = self.condtype() @@ -355,17 +352,9 @@ b.wait_for_started() _wait() self.assertEqual(results1, []) - # FIXME: notify(n) is not currently implemented in Jython, trying - # repeated notifies instead. (and honestly w/o understanding what - # notify(n) really even means for CPython...). - # Notify 3 threads at first cond.acquire() - ###cond.notify(3) - cond.notify() - cond.notify() - cond.notify() - + cond.notify(3) _wait() phase_num = 1 cond.release() @@ -375,12 +364,7 @@ self.assertEqual(results2, []) # Notify 5 threads: they might be in their first or second wait cond.acquire() - ###cond.notify(5) - cond.notify() - cond.notify() - cond.notify() - cond.notify() - cond.notify() + cond.notify(5) _wait() phase_num = 2 cond.release() @@ -419,9 +403,8 @@ results.append(t2 - t1) Bunch(f, N).wait_for_finished() self.assertEqual(len(results), 5) - epsilon = 1e-5 # wait time is hard to test precisely, so keep low resolution for dt in results: - self.assertTrue(dt >= (0.2 - epsilon), dt) + self.assertTrue(dt >= 0.2, dt) class BaseSemaphoreTests(BaseTestCase): diff -r 5064a5c5b1a3 src/org/python/modules/_threading/AbstractLock.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/python/modules/_threading/AbstractLock.java Tue Apr 14 15:40:49 2015 -0500 @@ -0,0 +1,18 @@ +package org.python.modules._threading; + +import org.python.core.PyObject; + +abstract class AbstractLock extends PyObject +{ + final java.util.concurrent.locks.Lock _lock; + + AbstractLock(java.util.concurrent.locks.Lock lock) + { + this._lock = lock; + } + + public abstract boolean acquire(); + public abstract boolean acquire(boolean blocking); + public abstract void release(); + public abstract boolean _is_owned(); +} diff -r 5064a5c5b1a3 src/org/python/modules/_threading/Condition.java --- a/src/org/python/modules/_threading/Condition.java Mon Apr 13 23:17:20 2015 -0400 +++ b/src/org/python/modules/_threading/Condition.java Tue Apr 14 15:40:49 2015 -0500 @@ -1,5 +1,6 @@ package org.python.modules._threading; +import java.util.concurrent.locks.ReentrantLock; import org.python.core.ContextManager; import org.python.core.Py; import org.python.core.PyException; @@ -17,11 +18,16 @@ public class Condition extends PyObject implements ContextManager, Traverseproc { public static final PyType TYPE = PyType.fromClass(Condition.class); - private final Lock _lock; + private final AbstractLock _lock; private final java.util.concurrent.locks.Condition _condition; public Condition() { - this(new Lock()); + this(new RLock()); + } + + public Condition(AbstractLock lock) { + _lock = lock; + _condition = lock._lock.newCondition(); } public Condition(Lock lock) { @@ -34,7 +40,7 @@ PyType subtype, PyObject[] args, String[] keywords) { final int nargs = args.length; if (nargs == 1) { - return new Condition((Lock)args[0]); + return new Condition((AbstractLock)args[0]); } return new Condition(); } @@ -138,7 +144,7 @@ @ExposedMethod final boolean Condition__is_owned() { - return _lock._lock.isHeldByCurrentThread(); + return _lock._is_owned(); } diff -r 5064a5c5b1a3 src/org/python/modules/_threading/Lock.java --- a/src/org/python/modules/_threading/Lock.java Mon Apr 13 23:17:20 2015 -0400 +++ b/src/org/python/modules/_threading/Lock.java Tue Apr 14 15:40:49 2015 -0500 @@ -1,6 +1,7 @@ package org.python.modules._threading; -import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.AbstractQueuedSynchronizer; +import java.util.concurrent.TimeUnit; import org.python.core.ContextManager; import org.python.core.Py; import org.python.core.PyException; @@ -15,13 +16,16 @@ @Untraversable @ExposedType(name = "_threading.Lock") -public class Lock extends PyObject implements ContextManager { +public class Lock extends AbstractLock implements ContextManager { public static final PyType TYPE = PyType.fromClass(Lock.class); - final ReentrantLock _lock; - + // see http://bugs.jython.org/issue2328 - need to support another thread + // releasing this lock, per CPython semantics, so support semantics with + // custom non-reentrant lock, not a ReentrantLock + private final Mutex _mlock; public Lock() { - _lock = new ReentrantLock(); + super(new Mutex()); + this._mlock = (Mutex)this._lock; } @ExposedNew @@ -30,7 +34,6 @@ final int nargs = args.length; return new Lock(); } - @ExposedMethod(defaults = "true") final boolean Lock_acquire(boolean blocking) { @@ -63,9 +66,6 @@ @ExposedMethod final void Lock_release() { - if (!_lock.isHeldByCurrentThread() || _lock.getHoldCount() <= 0) { - throw Py.RuntimeError("cannot release un-acquired lock"); - } _lock.unlock(); } @@ -86,7 +86,7 @@ @ExposedMethod final boolean Lock_locked() { - return _lock.isLocked(); + return _mlock.isLocked(); } public boolean locked() { @@ -95,10 +95,62 @@ @ExposedMethod final boolean Lock__is_owned() { - return _lock.isHeldByCurrentThread(); + return _mlock.isLocked(); } public boolean _is_owned() { return Lock__is_owned(); } + + private static class Mutex implements java.util.concurrent.locks.Lock { + + // Our internal helper class + private static class Sync extends AbstractQueuedSynchronizer { + // Reports whether in locked state + protected boolean isHeldExclusively() { + return getState() == 1; + } + + // Acquires the lock if state is zero + public boolean tryAcquire(int acquires) { + assert acquires == 1; // Otherwise unused + if (compareAndSetState(0, 1)) { + setExclusiveOwnerThread(Thread.currentThread()); + return true; + } + return false; + } + + // Releases the lock by setting state to zero + protected boolean tryRelease(int releases) { + assert releases == 1; // Otherwise unused + if (getState() == 0) throw new IllegalMonitorStateException(); + setExclusiveOwnerThread(null); + setState(0); + return true; + } + + // Provides a Condition + ConditionObject newCondition() { return new ConditionObject(); } + } + + // The sync object does all the hard work. We just forward to it. + private final Sync sync = new Sync(); + + public void lock() { sync.acquire(1); } + public boolean tryLock() { return sync.tryAcquire(1); } + public void unlock() { sync.release(1); } + public java.util.concurrent.locks.Condition newCondition() { return sync.newCondition(); } + public boolean isLocked() { return sync.isHeldExclusively(); } + public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); } + public void lockInterruptibly() throws InterruptedException { + sync.acquireInterruptibly(1); + } + public boolean tryLock(long timeout, TimeUnit unit) + throws InterruptedException { + return sync.tryAcquireNanos(1, unit.toNanos(timeout)); + } + } + + } diff -r 5064a5c5b1a3 src/org/python/modules/_threading/RLock.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/python/modules/_threading/RLock.java Tue Apr 14 15:40:49 2015 -0500 @@ -0,0 +1,105 @@ +package org.python.modules._threading; + +import java.util.concurrent.locks.ReentrantLock; +import org.python.core.ContextManager; +import org.python.core.Py; +import org.python.core.PyException; +import org.python.core.PyNewWrapper; +import org.python.core.PyObject; +import org.python.core.PyType; +import org.python.core.ThreadState; +import org.python.core.Untraversable; +import org.python.expose.ExposedMethod; +import org.python.expose.ExposedNew; +import org.python.expose.ExposedType; + +@Untraversable +@ExposedType(name = "_threading.RLock") +public class RLock extends AbstractLock implements ContextManager { + + public static final PyType TYPE = PyType.fromClass(RLock.class); + private final ReentrantLock _rlock; + + public RLock() { + super(new ReentrantLock()); + this._rlock = (ReentrantLock)this._lock; + } + + @ExposedNew + final static PyObject RLock___new__ (PyNewWrapper new_, boolean init, + PyType subtype, PyObject[] args, String[] keywords) { + final int nargs = args.length; + return new Lock(); + } + + + @ExposedMethod(defaults = "true") + final boolean RLock_acquire(boolean blocking) { + if (blocking) { + _lock.lock(); + return true; + } else { + return _lock.tryLock(); + } + } + + public boolean acquire() { + return RLock_acquire(true); + } + + public boolean acquire(boolean blocking) { + return RLock_acquire(blocking); + } + + @ExposedMethod + final PyObject RLock___enter__() { + _lock.lock(); + return this; + } + + public PyObject __enter__(ThreadState ts) { + _lock.lock(); + return this; + } + + @ExposedMethod + final void RLock_release() { + if (!_rlock.isHeldByCurrentThread() || _rlock.getHoldCount() <= 0) { + throw Py.RuntimeError("cannot release un-acquired lock"); + } + _lock.unlock(); + } + + public void release() { + RLock_release(); + } + + @ExposedMethod + final boolean RLock___exit__(PyObject type, PyObject value, PyObject traceback) { + _lock.unlock(); + return false; + } + + public boolean __exit__(ThreadState ts, PyException exception) { + _lock.unlock(); + return false; + } + + @ExposedMethod + final boolean RLock_locked() { + return _rlock.isLocked(); + } + + public boolean locked() { + return RLock_locked(); + } + + @ExposedMethod + final boolean RLock__is_owned() { + return _rlock.isHeldByCurrentThread(); + } + + public boolean _is_owned() { + return RLock__is_owned(); + } +} diff -r 5064a5c5b1a3 src/org/python/modules/_threading/_threading.java --- a/src/org/python/modules/_threading/_threading.java Mon Apr 13 23:17:20 2015 -0400 +++ b/src/org/python/modules/_threading/_threading.java Tue Apr 14 15:40:49 2015 -0500 @@ -10,9 +10,9 @@ public static void classDictInit(PyObject dict) { dict.__setitem__("__name__", Py.newString("_threading")); dict.__setitem__("Lock", Lock.TYPE); - dict.__setitem__("RLock", Lock.TYPE); + dict.__setitem__("RLock", RLock.TYPE); dict.__setitem__("_Lock", Lock.TYPE); - dict.__setitem__("_RLock", Lock.TYPE); + dict.__setitem__("_RLock", RLock.TYPE); dict.__setitem__("Condition", Condition.TYPE); // Hide from Python