/** * Receive error for a Observer. Throw the error up the chain and stop processing. * * @param w */ void error(ZipObserver<T, ?> w, Exception e) { if (running.compareAndSet(true, false)) { // this thread succeeded in setting running=false so let's propagate the error observer.onError(e); /* since we receive an error we want to tell everyone to stop */ stop(); } }
/** * Receive notification of a Observer completing its iterations. * * @param w */ void complete(ZipObserver<T, ?> w) { // store that this ZipObserver is completed completed.put(w, Boolean.TRUE); // if all ZipObservers are completed, we mark the whole thing as completed if (completed.size() == observers.size()) { if (running.compareAndSet(true, false)) { // this thread succeeded in setting running=false so let's propagate the completion // mark ourselves as done observer.onCompleted(); } } }
/** * Receive the next value from a Observer. * * <p>If we have received values from all Observers, trigger the zip function, otherwise store * the value and keep waiting. * * @param w * @param arg */ void next(ZipObserver<T, ?> w, Object arg) { if (observer == null) { throw new RuntimeException("This shouldn't be running if a Observer isn't registered"); } /* if we've been 'unsubscribed' don't process anything further even if the things we're watching keep sending (likely because they are not responding to the unsubscribe call) */ if (!running.get()) { return; } // store the value we received and below we'll decide if we are to send it to the Observer receivedValuesPerObserver.get(w).add(arg); // define here so the variable is out of the synchronized scope Object[] argsToZip = new Object[observers.size()]; /* we have to synchronize here despite using concurrent data structures because the compound logic here must all be done atomically */ synchronized (this) { // if all ZipObservers in 'receivedValues' map have a value, invoke the zipFunction for (ZipObserver<T, ?> rw : receivedValuesPerObserver.keySet()) { if (receivedValuesPerObserver.get(rw).peek() == null) { // we have a null meaning the queues aren't all populated so won't do anything return; } } // if we get to here this means all the queues have data int i = 0; for (ZipObserver<T, ?> rw : observers) { argsToZip[i++] = receivedValuesPerObserver.get(rw).remove(); } } // if we did not return above from the synchronized block we can now invoke the zipFunction // with all of the args // we do this outside the synchronized block as it is now safe to call this concurrently and // don't need to block other threads from calling // this 'next' method while another thread finishes calling this zipFunction observer.onNext(zipFunction.call(argsToZip)); }