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 17:40:45 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/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 17:40:45 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,16 +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 ConditionSupportingLock _lock; private final java.util.concurrent.locks.Condition _condition; public Condition() { - this(new Lock()); + this(new RLock()); } - public Condition(Lock lock) { + public Condition(ConditionSupportingLock lock) { _lock = lock; - _condition = lock._lock.newCondition(); + _condition = lock.getLock().newCondition(); } @ExposedNew @@ -34,7 +35,7 @@ PyType subtype, PyObject[] args, String[] keywords) { final int nargs = args.length; if (nargs == 1) { - return new Condition((Lock)args[0]); + return new Condition((ConditionSupportingLock)args[0]); } return new Condition(); } @@ -102,13 +103,15 @@ } public void notify$() { - Condition_notify(); + Condition_notify(1); } - @ExposedMethod - final void Condition_notify() { + @ExposedMethod(defaults = "1") + final void Condition_notify(int count) { try { - _condition.signal(); + for( int i = 0; i < count; i++) { + _condition.signal(); + } } catch (IllegalMonitorStateException ex) { throw Py.RuntimeError("cannot notify on un-acquired lock"); } @@ -138,14 +141,14 @@ @ExposedMethod final boolean Condition__is_owned() { - return _lock._lock.isHeldByCurrentThread(); + return _lock._is_owned(); } /* Traverseproc implementation */ @Override public int traverse(Visitproc visit, Object arg) { - return _lock != null ? visit.visit(_lock, arg) : 0; + return _lock != null ? visit.visit((PyObject)_lock, arg) : 0; } @Override diff -r 5064a5c5b1a3 src/org/python/modules/_threading/ConditionSupportingLock.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/python/modules/_threading/ConditionSupportingLock.java Tue Apr 14 17:40:45 2015 -0500 @@ -0,0 +1,16 @@ +package org.python.modules._threading; + +import org.python.core.PyObject; + +/** + * The protocol needed for a Lock object to work with a Condition + * object. + */ +interface ConditionSupportingLock +{ + public java.util.concurrent.locks.Lock getLock(); + 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/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 17:40:45 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,20 @@ @Untraversable @ExposedType(name = "_threading.Lock") -public class Lock extends PyObject implements ContextManager { +public class Lock extends PyObject implements ContextManager, ConditionSupportingLock { 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 _lock; public Lock() { - _lock = new ReentrantLock(); + this._lock = new Mutex(); + } + + public java.util.concurrent.locks.Lock getLock() { + return _lock; } @ExposedNew @@ -30,7 +38,6 @@ final int nargs = args.length; return new Lock(); } - @ExposedMethod(defaults = "true") final boolean Lock_acquire(boolean blocking) { @@ -63,9 +70,6 @@ @ExposedMethod final void Lock_release() { - if (!_lock.isHeldByCurrentThread() || _lock.getHoldCount() <= 0) { - throw Py.RuntimeError("cannot release un-acquired lock"); - } _lock.unlock(); } @@ -95,10 +99,62 @@ @ExposedMethod final boolean Lock__is_owned() { - return _lock.isHeldByCurrentThread(); + return _lock.isLocked(); } public boolean _is_owned() { return Lock__is_owned(); } + } + + +final 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 17:40:45 2015 -0500 @@ -0,0 +1,108 @@ +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 PyObject implements ContextManager, ConditionSupportingLock { + + public static final PyType TYPE = PyType.fromClass(RLock.class); + private final ReentrantLock _lock; + + public RLock() { + this._lock = new ReentrantLock(); + } + + public java.util.concurrent.locks.Lock getLock() { + return _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 (!_lock.isHeldByCurrentThread() || _lock.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 _lock.isLocked(); + } + + public boolean locked() { + return RLock_locked(); + } + + @ExposedMethod + final boolean RLock__is_owned() { + return _lock.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 17:40:45 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