Skip to content

Commit df81f17

Browse files
daschlMichael Nitschinger
authored andcommitted
SPY-167: Avoid deadlock on notify listeners.
Motivation ---------- Because of how the addListener and notifyListener works, it could be that one deadlocks each other because they are waiting on the same locks from different threads. Modifications ------------- The listeners are copied before notified, moving it out of the synch block. Result ------ Since notify is not synched anymore, the lock should be not happening anymore. Note that also the listeners array is set to empty which avoid notifying listeners more than once potentially. Thanks to @adamhonen for pointing out the fix. Change-Id: I7ebda58c2eadd62d8885e3eeac79d5a971e07979 Reviewed-on: http://review.couchbase.org/36219 Reviewed-by: Matt Ingenthron <matt@couchbase.com> Tested-by: Michael Nitschinger <michael.nitschinger@couchbase.com>
1 parent 386a843 commit df81f17

File tree

1 file changed

+26
-24
lines changed

1 file changed

+26
-24
lines changed

src/main/java/net/spy/memcached/internal/AbstractListenableFuture.java

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* Copyright (C) 2006-2009 Dustin Sallings
3-
* Copyright (C) 2009-2013 Couchbase, Inc.
3+
* Copyright (C) 2009-2014 Couchbase, Inc.
44
*
55
* Permission is hereby granted, free of charge, to any person obtaining a copy
66
* of this software and associated documentation files (the "Software"), to deal
@@ -23,11 +23,12 @@
2323

2424
package net.spy.memcached.internal;
2525

26+
import net.spy.memcached.compat.SpyObject;
27+
2628
import java.util.ArrayList;
2729
import java.util.List;
2830
import java.util.concurrent.ExecutorService;
2931
import java.util.concurrent.Future;
30-
import net.spy.memcached.compat.SpyObject;
3132

3233
/**
3334
* The {@link AbstractListenableFuture} implements common functionality shared
@@ -55,7 +56,12 @@ public abstract class AbstractListenableFuture
5556
*/
5657
private List<GenericCompletionListener<? extends Future<T>>> listeners;
5758

58-
public AbstractListenableFuture(ExecutorService executor) {
59+
/**
60+
* Creates a new {@link AbstractListenableFuture}.
61+
*
62+
* @param executor the executor in which the callbacks will be executed in.
63+
*/
64+
protected AbstractListenableFuture(ExecutorService executor) {
5965
service = executor;
6066
listeners = new ArrayList<GenericCompletionListener<? extends Future<T>>>();
6167
}
@@ -79,24 +85,19 @@ protected ExecutorService executor() {
7985
* @return the current future to allow chaining.
8086
*/
8187
protected Future<T> addToListeners(
82-
GenericCompletionListener<? extends Future<T>> listener) {
88+
final GenericCompletionListener<? extends Future<T>> listener) {
8389
if (listener == null) {
8490
throw new IllegalArgumentException("The listener can't be null.");
8591
}
8692

87-
if(isDone()) {
88-
notifyListener(executor(), this, listener);
89-
return this;
93+
synchronized(this) {
94+
listeners.add(listener);
9095
}
9196

92-
synchronized(this) {
93-
if (!isDone()) {
94-
listeners.add(listener);
95-
return this;
96-
}
97+
if(isDone()) {
98+
notifyListeners();
9799
}
98100

99-
notifyListener(executor(), this, listener);
100101
return this;
101102
}
102103

@@ -117,7 +118,7 @@ public void run() {
117118
} catch(Throwable t) {
118119
getLogger().warn(
119120
"Exception thrown wile executing " + listener.getClass().getName()
120-
+ ".operationComplete()", t);
121+
+ ".operationComplete()", t);
121122
}
122123
}
123124
});
@@ -139,9 +140,15 @@ protected void notifyListeners() {
139140
*
140141
* @param future the future to pass on to the listeners.
141142
*/
142-
protected synchronized void notifyListeners(final Future<?> future) {
143+
protected void notifyListeners(final Future<?> future) {
144+
final List<GenericCompletionListener<? extends Future<T>>> copy =
145+
new ArrayList<GenericCompletionListener<? extends Future<T>>>();
146+
synchronized(this) {
147+
copy.addAll(listeners);
148+
listeners = new ArrayList<GenericCompletionListener<? extends Future<T>>>();
149+
}
143150
for (GenericCompletionListener<? extends Future<? super T>> listener
144-
: listeners) {
151+
: copy) {
145152
notifyListener(executor(), future, listener);
146153
}
147154
}
@@ -158,16 +165,11 @@ protected Future<T> removeFromListeners(
158165
throw new IllegalArgumentException("The listener can't be null.");
159166
}
160167

161-
if(isDone()) {
162-
return this;
163-
}
164-
165-
synchronized(this) {
166-
if (!isDone()) {
168+
if (!isDone()) {
169+
synchronized(this) {
167170
listeners.remove(listener);
168171
}
169172
}
170-
171173
return this;
172174
}
173-
}
175+
}

0 commit comments

Comments
 (0)