Working with Firebase in Java, I have a situation where it seems my Transaction.Handler.onComplete() is never called.
I'm trying to wait for completion on a particular transaction, so I keep the calling thread from advancing until onComplete() is called. This is done by an anonymous decorator around the existing Transaction.Handler txn.
Transaction.Handler txn = ...
CountDownLatch latch = new CountDownLatch(1);
ref.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData data) {
return txn.doTransaction(data);
}
#Override
public void onComplete(FirebaseError e, boolean wasCommitted, DataSnapshot data) {
System.out.println("never printed");
txn.onComplete(e, wasCommitted, data);
if(e != null || !wasCommitted) {
System.out.println("error: " + e + ", committed: " + wasCommitted);
}
latch.countDown();
}
});
latch.await();
System.out.println("never printed");
doTransaction() definitely completes at least once - and I can see the data update in the web interface - but onComplete() is never called and we just hang.
I can't see anything wrong with this wrapper pattern: I ran the same transaction alone in a test main() method on a different Firebase, and onComplete() was called.
What is causing this hang? Is there something about a Firebase's configuration (e.g. auth? rules? ) that's introducing a bug here?
EDIT
I'm having the same problem without the wrapper pattern. Here's that code:
CountDownLatch latch = new CountDownLatch(1);
ref.runTransaction(new Transaction.Handler() {
#Override
public void onComplete(FirebaseError e, boolean b, DataSnapshot data) {
if(e != null || !b) {
System.out.println("error: " + e + ", committed: " + b);
}
System.out.println("transaction finished!");
latch.countDown();
}
#Override
public Result doTransaction(MutableData data) {
System.out.println("doing transaction");
data.child(...).setValue(...);
return Transaction.success(data);
}
});
latch.await();
System.out.println("never printed");
Which results in printing doing transaction and the data changing in the Firebase (which I see via web UI), but not printing transaction finished. It just hangs.
On Android all Firebase callbacks are run on the main thread. If you call latch.await() on the main thread, onComplete will never have the chance to run, because the main thread is blocked, resulting in a deadlock.
Related
autoSearch_button.setOnClickListener(view -> {
grant_permission();
check_location_enableornot();
lastButton_pressed = view.getId();
ExampleRunnable exampleRunnable = new ExampleRunnable(20);
Thread thread = new Thread(exampleRunnable);
thread.start();
thread.join();
});
public class ExampleRunnable implements Runnable {
private int kmRadius;
private double lat =0 , longi =0 ;
public ExampleRunnable(int i) {
this.kmRadius = i;
}
#Override
public void run() {
lastLocation();
}
private void lastLocation(){
Log.i(TAG, "lastLocation: " + Thread.currentThread().getName());
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(MainActivity.this);
// for now being assume that i have declared
#SuppressLint("MissingPermission") Task<Location> locationTask = fusedLocationProviderClient.getLastLocation();
locationTask.addOnSuccessListener(location -> {
if (location != null) {
//We have a location
Log.d(TAG, "run: last location" + Thread.currentThread().getName()););
this.lat = location.getLatitude();
this.longi = location.getLongitude();
print();
} else {
Log.d(TAG, "onSuccess: Location was null... calling robust");
}
}).addOnFailureListener(e -> Log.e(TAG, "onFailure: " + e.getLocalizedMessage() ));
}
}
public synchronized void print(){
Log.d(TAG, "print: " + Thread.currentThread.getName());
}
}
Output what i want in logcat
lastlocation: thread4
run: lastLocation thread4
print: thread4
But what result i am getting -
lastlocation: thread4 //
run: lastLocation main //
print: main
I want to handle location and working over it in a particular thread
locationTask.addOnSuccessListener
This doesn't run the listener, it merely registers the block of code that follows with the locationTask object, and that object can do whatever it wants with it.
Evidently (and this is common with event handler systems like this), some thread ends up executing some event, and as a consequence of this, the listeners for that event are run right then and there, in that thread.
You have two solutions:
Cause whatever event ends up triggering the listener (your code doesn't help explain where that is; the thing that causes the object that variable locationTask is pointing at to enter the 'success' state, thus triggering your listener) to occur in the thread you want it to occur in, and not main.
Don't fire up a thread to register a success listener; fire up a thread within your success listener (so instead of Log.d(TAG, "run: last location"..., start a thread).
Sometimes, event handler systems are configurable and you can tell it to fire such events in other threads, but this is rare. If the library doesn't support it (and I doubt it does), you'd have to write wrapper methods or wrap the entire library to get this.
I've been attempting to determine why the following code inside the .flatMap() operator was shown to be running on the main thread:
public Observable<Void> getObservable() {
return jobServiceObservable
.flatMap(jobService -> {
if (Looper.myLooper() == Looper.getMainLooper()) {
Log.d("LooperTest", "On main thread.");
} else {
Log.d("LooperTest", "Not on main thread.");
}
return jobService.syncReservations(accountUtil.getCurrentAccount());
})
.subscribeOn(Schedulers.io()).observeOn(foregroundScheduler);
}
As you can see, .subscribeOn() is being called with Schedulers.io(), however the log statements show that the code inside the .flatMap() was run on the main thread:
LooperTest: On main thread.
As a sanity check, I added extra calls to .subscribeOn(Schedulers.io()) to various parts of this code:
public Observable<Void> getObservable() {
return jobServiceObservable.subscribeOn(Schedulers.io())
.flatMap(jobService -> {
if (Looper.myLooper() == Looper.getMainLooper()) {
Log.d("LooperTest", "On main thread.");
} else {
Log.d("LooperTest", "Not on main thread.");
}
return jobService.syncReservations(accountUtil.getCurrentAccount()).subscribeOn(Schedulers.io());
})
.subscribeOn(Schedulers.io()).observeOn(foregroundScheduler);
}
However, the log statements appear to show the same result. Next, without any code changes, I cleaned the build and restarted my emulator. Upon the next run, the following was printed:
LooperTest: Not on main thread.
This was odd, as no code changes had been made. Again, without code changes, I cleaned the build and restarted the emulator. On the next run, the following was printed:
LooperTest: On main thread.
Once again, I cleaned the build, and then closed and opened a new emulator of a different type. Upon running, the following was printed:
LooperTest: Not on main thread.
Why is this occurring? I suspect there is some odd caching mechanism at play.
Additionally, note that jobService.syncReservations() returns a BehaviorSubject. Through various searches, it appears that Subjects may or may not respect calls to .subscribeOn().
Finally, note that jobServiceObservable is injected into the file where the above code is defined. The jobServiceObservable is created via the following code:
public Observable<JobService> getObservable() {
return Observable.create(e -> {
if (jobServiceBound && jobService != null) {
e.onNext(jobService);
e.onComplete();
} else {
jobServiceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
JobService.JobServiceBinder binder = (JobService.JobServiceBinder) service;
jobService = binder.getService();
jobServiceBound = true;
e.onNext(jobService);
e.onComplete();
}
#Override
public void onServiceDisconnected(ComponentName name) {
reset();
}
};
try {
boolean success = context.bindService(new Intent(context, JobService.class), jobServiceConnection, Context.BIND_AUTO_CREATE);
if (!success) {
e.onError(new Throwable("The service failed to be bound."));
}
} catch (SecurityException exception) {
e.onError(exception);
}
}
});
}
An authoritative answer on why the above behavior is occurring is needed.
Because onServiceConnected is called on the main thread by the system way after you subscribed to the wrapping Observable on the io() scheduler. subscribeOn tells where further subscription, or in this case, the body of Observable.create() should execute. You should use observeOn before the flatMap so that the mapper function gets executed on the desired thread:
public Observable<Void> getObservable() {
return jobServiceObservable
.observeOn(Schedulers.io())
.flatMap(jobService -> {
if (Looper.myLooper() == Looper.getMainLooper()) {
Log.d("LooperTest", "On main thread.");
} else {
Log.d("LooperTest", "Not on main thread.");
}
return jobService.syncReservations(
accountUtil.getCurrentAccount()).subscribeOn(Schedulers.io());
})
.observeOn(foregroundScheduler);
}
(In contrast, with typical Retrofit network calls, subscribeOn works because the network library executes its blocking call on the given scheduler and stays there for the emission of the network response.)
There are two issues which I am currently facing.
1) As soon as the line RetrofitProvider.getInstance().getCurrentWeather(.....) is called the network call is being done. How can it be deferred till the observer is connected to it.
2) Once weatherInfoPublisher.onComplete() is called, the next time I call onComplete on this object the new observer's onNext is not getting called.
public Observable<LinkedList<WeatherInfo>> getWeatherData(final String payload, final TempUnit tempUnit) {
PublishSubject weatherInfoPublisher = PublishSubject.create();
RetrofitProvider.getInstance().getCurrentWeather(payload + ",us", translateTempUnit(tempUnit))
.flatMap(new Function<String, ObservableSource<String>>() {
#Override
public ObservableSource<String> apply(String todayResponse) throws Exception {
Log.d(TAG, "Received today weather: " + todayResponse);
parseTodayData(todayResponse, weatherDataList);
return RetrofitProvider.getInstance().getForecastWeather(
payload + ",us", translateTempUnit(tempUnit), FORECAST_DAYS);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableObserver<String>() {
#Override
public void onNext(String futureResponse) {
Log.d(TAG, "Received future weather: " + futureResponse);
parseFutureData(futureResponse, weatherDataList);
weatherInfoPublisher.onNext(weatherDataList);
weatherInfoPublisher.onComplete();
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "The error is, " + e.getMessage());
}
#Override
public void onComplete() {
}
});
return weatherInfoPublisher;
}
This is a singleton class and the entire implementation has been provided in here in Github Link.
How can it be deferred till the observer is connected to it.
Do not subscribe to that observable in this method. Instead return that observable to the client. As soon as the observable is subscribed - a request would be performed.
the next time I call onComplete on this object the new observer's onNext is not getting called.
See reactive stream specs: if a stream completes - it can never be continued, that's a terminal event.
I have the following construct in code
public boolean do(){
final boolean[] returnValue = new boolean[1];
final CountDownLatch cdl = new CountDownLatch(1);
event.addListener(new Listener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Log.d(TAG, "onDataChange");
returnValue[0] = true;
cdl.countDown();
}
});
try {
if (cdl.await(1L, TimeUnit.MINUTES)) {
return returnValue[0];
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
What happens is the CountDownLatch waits for 1 Minute and then the Listener triggers. But since the latch has already counted down false is always returned. To me it seems as if the Latch is blocking the entire thread and not letting the Async Callback happen.
I have tried wrapping the event.addListener()... part in a Runnable but the Problem persists.
I have the exact same construct in another part of my code and there it works.
Edit:
I have put Logs for Thread.currentThread().getId() and it does, in fact, evaluate to the same Id. Shouldn't the Async Callback be in a different Thread?
I have an application that uses a ConnectableObservable that runs for a long time. Mysteriously after some time its observer stopped getting notifications in its onNext() method.
I have written the following test that simplifies the example. It's just a ConnectableObservable with an infinite loop, with one subscriber using both observeOn and subscribeon. After 128 s.onNext(1) calls it stops notifying the observer.
#Test
public void testHotObservable() throws InterruptedException{
CountDownLatch latch = new CountDownLatch(1);
ConnectableObservable<Integer> observable = Observable.<Integer>create( (s) -> {
while(true){
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
s.onNext(1);
}
})
.observeOn(Schedulers.io())
.subscribeOn(Schedulers.io())
.publish();
Observer<Integer> observer = new Observer<Integer>() {
#Override
public void onNext(Integer i) {
System.out.println("got "+i);
}
#Override
public void onCompleted() {
System.out.println("completed");
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
}
};
observable.subscribe(observer);
observable.connect();
latch.await();
}
This is what I've seen debugging RxJava's code I have found out the reason why it doesn't call the Observer's onNext() method but I don't understand it:
1.- s.onNext(1); is called:
2.- The execution gets to rx.internal.operators.OperatorObserveOn.ObserveOnSubscriber.pollQueue():
void pollQueue() {
int emitted = 0;
final AtomicLong localRequested = this.requested;
final AtomicLong localCounter = this.counter;
do {
localCounter.set(1);
long produced = 0;
long r = localRequested.get();
for (;;) {
...
System.out.println("R: "+r);
if (r > 0) {
Object o = queue.poll();
if (o != null) {
child.onNext(on.getValue(o));
r--;
The problem is the value of r. The first time it executes its value is always 128. After each call it decrements by 1 (r--). This means that ConnectableObservable can only notify its observers 128 times when using both observeOn and subscribeOn. If I remove subscribeOn, r's value starts over each iteration and it works.
UPDATE:
I found a solution: the problem was caused by the order of the .observerOn().subscribeOn(). If I reverse it to .subscribeOn().observeOn() it works (I can see that the value of r is always reset to 128).
Anyway I'd appreciate an explanation.
Many async operators use internal, fixed size buffers and rely on subscribers requesting requently. In your case, something doesn't request properly which I can't say what it is. I suggest trying your use case with standard components to see what could be wrong, i.e., you can replace your custom Observable with a PublishSubject + sample:
Subject<Integer, Integer> source = PublishSubject.<Integer>create().toSerialized();
ConnectableObservable<Integer> co = source.sample(
500, TimeUnit.MILLISECONDS, Schedulers.io())
.onBackpressureBuffer().publish();
co.subscribe(yourSubscriber);
co.connect();
source.onNext(1);