Skip to content

Commit 55ea575

Browse files
Catch and handle Errors raised by PromiseListeners in SettablePromiseImpl
When a settable promise has been resolved we notify all listeners and then wake up any waiting threads by counting down on the _awaitLatch. However, if one of the listeners raised an Error (not Exception) it would skip subsequent listeners and count down on the _awaitLatch. This change ensures that Errors are caught and do not abnormally terminal notifications during SettablePromiseImpl's resolution.
1 parent c365f39 commit 55ea575

File tree

2 files changed

+48
-1
lines changed

2 files changed

+48
-1
lines changed

src/com/linkedin/parseq/promise/SettablePromiseImpl.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,17 @@ private void doFinish(T value, Throwable error) throws PromiseResolvedException
145145

146146
private void notifyListener(final PromiseListener<T> listener)
147147
{
148+
// We intentionally catch Throwable around the listener invocation because
149+
// it will cause the notifier loop and subsequent count down in doFinish to
150+
// be skipped, which will certainly lead to bad behavior. It could be argued
151+
// that the catch should not apply for use of notifyListener from
152+
// addListener, but it seems better to err on the side of consistency and
153+
// least surprise.
148154
try
149155
{
150156
listener.onResolved(this);
151157
}
152-
catch (Exception e)
158+
catch (Throwable e)
153159
{
154160
LOGGER.warn("An exception was thrown by listener: " + listener.getClass(), e);
155161
}

test/com/linkedin/parseq/promise/TestSettablePromise.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,4 +323,45 @@ public void onResolved(Promise<String> resolvedPromise)
323323
latch.await();
324324
assertEquals("done!", resultRef.get());
325325
}
326+
327+
@Test
328+
public void testListenerThrowsException() throws InterruptedException
329+
{
330+
// This test ensures that we properly resolve the promise even when
331+
// one of its listeners throws an Error.
332+
final SettablePromise<String> promise = Promises.settable();
333+
promise.addListener(new PromiseListener<String>()
334+
{
335+
@Override
336+
public void onResolved(Promise<String> promise)
337+
{
338+
throw new RuntimeException();
339+
}
340+
});
341+
342+
promise.done("Done!");
343+
assertTrue(promise.await(5, TimeUnit.SECONDS));
344+
assertEquals("Done!", promise.get());
345+
}
346+
347+
348+
@Test
349+
public void testListenerThrowsError() throws InterruptedException
350+
{
351+
// This test ensures that we properly resolve the promise even when
352+
// one of its listeners throws an Error.
353+
final SettablePromise<String> promise = Promises.settable();
354+
promise.addListener(new PromiseListener<String>()
355+
{
356+
@Override
357+
public void onResolved(Promise<String> promise)
358+
{
359+
throw new Error();
360+
}
361+
});
362+
363+
promise.done("Done!");
364+
assertTrue(promise.await(5, TimeUnit.SECONDS));
365+
assertEquals("Done!", promise.get());
366+
}
326367
}

0 commit comments

Comments
 (0)