-
Notifications
You must be signed in to change notification settings - Fork 7.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Moved to atomic field updaters. #1246
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,7 @@ | |
import java.util.NoSuchElementException; | ||
import java.util.concurrent.ArrayBlockingQueue; | ||
import java.util.concurrent.BlockingQueue; | ||
import java.util.concurrent.atomic.AtomicBoolean; | ||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; | ||
|
||
import rx.Notification; | ||
import rx.Observable; | ||
|
@@ -61,10 +61,16 @@ private NextIterator(NextObserver<? extends T> observer) { | |
this.observer = observer; | ||
} | ||
|
||
|
||
// in tests, set the waiting flag without blocking for the next value to | ||
// allow lockstepping instead of multi-threading | ||
void setWaiting(boolean value) { | ||
observer.waiting.set(value); | ||
/** | ||
* In tests, set the waiting flag without blocking for the next value to | ||
* allow lockstepping instead of multi-threading | ||
* @param value use 1 to enter into the waiting state | ||
*/ | ||
void setWaiting(int value) { | ||
observer.setWaiting(value); | ||
} | ||
|
||
@Override | ||
|
@@ -135,7 +141,10 @@ public void remove() { | |
|
||
private static class NextObserver<T> extends Subscriber<Notification<? extends T>> { | ||
private final BlockingQueue<Notification<? extends T>> buf = new ArrayBlockingQueue<Notification<? extends T>>(1); | ||
private final AtomicBoolean waiting = new AtomicBoolean(false); | ||
volatile int waiting; | ||
@SuppressWarnings("rawtypes") | ||
static final AtomicIntegerFieldUpdater<NextObserver> WAITING_UPDATER | ||
= AtomicIntegerFieldUpdater.newUpdater(NextObserver.class, "waiting"); | ||
|
||
@Override | ||
public void onCompleted() { | ||
|
@@ -150,7 +159,7 @@ public void onError(Throwable e) { | |
@Override | ||
public void onNext(Notification<? extends T> args) { | ||
|
||
if (waiting.getAndSet(false) || !args.isOnNext()) { | ||
if (WAITING_UPDATER.getAndSet(this, 0) == 1 || !args.isOnNext()) { | ||
Notification<? extends T> toOffer = args; | ||
while (!buf.offer(toOffer)) { | ||
Notification<? extends T> concurrentItem = buf.poll(); | ||
|
@@ -165,9 +174,11 @@ public void onNext(Notification<? extends T> args) { | |
} | ||
|
||
public Notification<? extends T> takeNext() throws InterruptedException { | ||
waiting.set(true); | ||
setWaiting(1); | ||
return buf.take(); | ||
} | ||
|
||
void setWaiting(int value) { | ||
WAITING_UPDATER.lazySet(this, value); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Help me understand why lazySet is safe to use here. It seems we want to have visibility of this value when we next read, but my understanding of lazySet is that it does not guarantee that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I read up further on lazySet and added another comment to code above. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The value set here is eventually visible, which may take a few nanoseconds. The benefit is that the lazySet doesn't flush the store buffer whereas a volatile write does, which incurs 10-40 nanosecond cache-coherency traffic. Generally, the lazySet only makes sure writes that happened before are not reordered after the lazy write; which is generally enough in a SPSC value transmission case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is eventually visible safe here? It doesn't seem to be. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My main guide was a blog you recommended: If you wish, I can eliminate all lazySet calls so there won't be any visibility concerns. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm trying to understand when it's safe. I get the use case for setting something null in cleanup, I'm more concerned in places where we set it and need to read it without any further writes guaranteed to happen before. |
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,8 +16,8 @@ | |
package rx.operators; | ||
|
||
import java.util.concurrent.ConcurrentLinkedQueue; | ||
import java.util.concurrent.atomic.AtomicBoolean; | ||
import java.util.concurrent.atomic.AtomicReference; | ||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; | ||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; | ||
|
||
import rx.Observer; | ||
import rx.Subscriber; | ||
|
@@ -53,12 +53,28 @@ public static <T> BufferUntilSubscriber<T> create() { | |
|
||
/** The common state. */ | ||
static final class State<T> { | ||
/** Lite notifications of type T. */ | ||
final NotificationLite<T> nl = NotificationLite.instance(); | ||
/** The first observer or the one which buffers until the first arrives. */ | ||
final AtomicReference<Observer<? super T>> observerRef = new AtomicReference<Observer<? super T>>(new BufferedObserver<T>()); | ||
volatile Observer<? super T> observerRef = new BufferedObserver<T>(); | ||
/** Allow a single subscriber only. */ | ||
final AtomicBoolean first = new AtomicBoolean(); | ||
volatile int first; | ||
/** Field updater for observerRef. */ | ||
@SuppressWarnings("rawtypes") | ||
static final AtomicReferenceFieldUpdater<State, Observer> OBSERVER_UPDATER | ||
= AtomicReferenceFieldUpdater.newUpdater(State.class, Observer.class, "observerRef"); | ||
/** Field updater for first. */ | ||
@SuppressWarnings("rawtypes") | ||
static final AtomicIntegerFieldUpdater<State> FIRST_UPDATER | ||
= AtomicIntegerFieldUpdater.newUpdater(State.class, "first"); | ||
|
||
boolean casFirst(int expected, int next) { | ||
return FIRST_UPDATER.compareAndSet(this, expected, next); | ||
} | ||
void setObserverRef(Observer<? super T> o) { | ||
OBSERVER_UPDATER.lazySet(this, o); | ||
} | ||
boolean casObserverRef(Observer<? super T> expected, Observer<? super T> next) { | ||
return OBSERVER_UPDATER.compareAndSet(this, expected, next); | ||
} | ||
} | ||
|
||
static final class OnSubscribeAction<T> implements OnSubscribe<T> { | ||
|
@@ -70,20 +86,21 @@ public OnSubscribeAction(State<T> state) { | |
|
||
@Override | ||
public void call(final Subscriber<? super T> s) { | ||
if (state.first.compareAndSet(false, true)) { | ||
if (state.casFirst(0, 1)) { | ||
final NotificationLite<T> nl = NotificationLite.instance(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NotificationLite is completely stateless so we can move this to a static reference can't we? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This applies to many other places in this PR as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Static field loses the type variable T and I have to stick SuppressWarnings everywhere. Besides, it is typical one makes local copies of variables in methods. In many places, I considered the option of using static, local or instance NotificationLite based on use and memory cost. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Copies in methods is no big deal as that is on the stack. At the object level it is different as that is on the heap. We should make sure we aren't creating new references on anything allocated per onNext at least. |
||
// drain queued notifications before subscription | ||
// we do this here before PassThruObserver so the consuming thread can do this before putting itself in the line of the producer | ||
BufferedObserver<? super T> buffered = (BufferedObserver<? super T>)state.observerRef.get(); | ||
BufferedObserver<? super T> buffered = (BufferedObserver<? super T>)state.observerRef; | ||
Object o; | ||
while ((o = buffered.buffer.poll()) != null) { | ||
state.nl.accept(s, o); | ||
nl.accept(s, o); | ||
} | ||
// register real observer for pass-thru ... and drain any further events received on first notification | ||
state.observerRef.set(new PassThruObserver<T>(s, buffered.buffer, state.observerRef)); | ||
state.setObserverRef(new PassThruObserver<T>(s, buffered.buffer, state)); | ||
s.add(Subscriptions.create(new Action0() { | ||
@Override | ||
public void call() { | ||
state.observerRef.set(Subscribers.empty()); | ||
state.setObserverRef(Subscribers.empty()); | ||
} | ||
})); | ||
} else { | ||
|
@@ -101,17 +118,17 @@ private BufferUntilSubscriber(State<T> state) { | |
|
||
@Override | ||
public void onCompleted() { | ||
state.observerRef.get().onCompleted(); | ||
state.observerRef.onCompleted(); | ||
} | ||
|
||
@Override | ||
public void onError(Throwable e) { | ||
state.observerRef.get().onError(e); | ||
state.observerRef.onError(e); | ||
} | ||
|
||
@Override | ||
public void onNext(T t) { | ||
state.observerRef.get().onNext(t); | ||
state.observerRef.onNext(t); | ||
} | ||
|
||
/** | ||
|
@@ -127,13 +144,13 @@ private static final class PassThruObserver<T> extends Subscriber<T> { | |
private final Observer<? super T> actual; | ||
// this assumes single threaded synchronous notifications (the Rx contract for a single Observer) | ||
private final ConcurrentLinkedQueue<Object> buffer; | ||
private final AtomicReference<Observer<? super T>> observerRef; | ||
private final NotificationLite<T> nl = NotificationLite.instance(); | ||
private final State<T> state; | ||
|
||
PassThruObserver(Observer<? super T> actual, ConcurrentLinkedQueue<Object> buffer, AtomicReference<Observer<? super T>> observerRef) { | ||
PassThruObserver(Observer<? super T> actual, ConcurrentLinkedQueue<Object> buffer, | ||
State<T> state) { | ||
this.actual = actual; | ||
this.buffer = buffer; | ||
this.observerRef = observerRef; | ||
this.state = state; | ||
} | ||
|
||
@Override | ||
|
@@ -155,20 +172,21 @@ public void onNext(T t) { | |
} | ||
|
||
private void drainIfNeededAndSwitchToActual() { | ||
final NotificationLite<T> nl = NotificationLite.instance(); | ||
Object o; | ||
while ((o = buffer.poll()) != null) { | ||
nl.accept(this, o); | ||
} | ||
// now we can safely change over to the actual and get rid of the pass-thru | ||
// but only if not unsubscribed | ||
observerRef.compareAndSet(this, actual); | ||
state.casObserverRef(this, actual); | ||
} | ||
|
||
} | ||
|
||
private static final class BufferedObserver<T> extends Subscriber<T> { | ||
private final ConcurrentLinkedQueue<Object> buffer = new ConcurrentLinkedQueue<Object>(); | ||
private final NotificationLite<T> nl = NotificationLite.instance(); | ||
private static final NotificationLite<Object> nl = NotificationLite.instance(); | ||
|
||
@Override | ||
public void onCompleted() { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we can use lazySet here. Take a look at this definition: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6275329
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should stay on the safe side with such constructors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The constructor might work (need to understand the memory model better for that) since the JMM might flush everything after construction.
I'm wondering if the
onNext
/onCompleted
/onError
is okay though asgetRecentValue()
may get a stale result. That may or may not be okay in this scenario. TheIterator
will keep checking. It seems like it is a race condition by definition so probably okay here?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The stuff I'm reading in the JMM about constructors is not answering my question. It's clear about final fields, but isn't clear on whether it causes the equivalent of a "volatile write" that would flush anything such as a lazySet. Thus, I think we need to assume it is not consistent unless we learn otherwise.