I am trying to send message to child actor from parent
but the message doesnot arrive until parent actor completes the onReceive block.
Is the any way to send message to child and wait result on the parent actor.
Parent:
onReveive(message) {
if(message instanceof ChildOrder) {
onChildOrder(message);
}
}
onChildOrder(message) {
Future<Object> future = Patterns.ask(childActorRef, order, timeout);
Object result = Await.result(future, timeout.duration()); /* this always times out */
}
Child:
onReveive(message) {
do stuff
}
As mentioned above
Object result = Await.result(future, timeout.duration());
line on onChildOrder method always times out and when the onReceive method of parent completes
the message ChildOrder arrives at the child's onReceive method.
It seems child cannot process any message without parent completes first.
Is there any way to send message to child and wait result. ?
You need the child to send the result back to the parent, otherwise the parent keeps waiting for an answer forever.
In Child:
#Override
public void onReceive(Object message) throws Exception {
MyResult res = doStuff(message);
// send result back
getSender().tell(res, getSelf());
}
It is indicated in the documentation of Patterns.ask:
[...] this means that the target actor needs to send the result to the sender reference provided.
Edit based on the comment:
Forward message from grandfather to child:
If you need the child to send back to the grand-parent, the parent may forward the message to the child so that to the child the sender is the grand-parent, therefore bypassing the parent on the way back:
In parent:
#Override
public void onReceive(Object message) throws Exception {
if(message instance of MessageFromGrandParent) {
child.forward(message, getContext());
}
// ...
}
Related
Context
I'm building a Flutter Plugin above the DJK SDK. For that, I have to implement the communication with the aircraft on the native side, and I'm doing it with Java. I'm also doing it only for Android.
One of the methods of the API is boolean connectToAircraft(), which must return if the connection with the aircraft succeeded.
Expected/current behavior
After I call connectToAircraft() - which invokes the DJISDKManager.getInstance().startConnectionToProduct() method, I expected to be able to use anything related to aircraft immediately, but this doesn't happen. I have to wait a few seconds before I can retrieve data from the aircraft.
Some code
public class UavApi implements IUavApi, DJISDKManager.SDKManagerCallback {
...
private final CountDownLatch onConnectToUavFinishedSignal = new CountDownLatch(1);
...
public boolean connectToUav() throws InterruptedException {
Logger.v("connectToUav()");
DJISDKManager.getInstance().startConnectionToProduct();
synchronized (onConnectToUavFinishedSignal) {
onConnectToUavFinishedSignal.await();
}
return DJISDKManager.getInstance().getProduct() instanceof Aircraft;
}
...
#Override
public void onProductConnect(#Nullable final BaseProduct baseProduct) {
Logger.v(MessageFormat.format("onProductConnect(product: {0})", baseProduct));
if (baseProduct != null) {
handleProductConnected(baseProduct);
}
}
#Override
public void onProductChanged(#Nullable final BaseProduct baseProduct) {
Logger.v(MessageFormat.format("onProductChanged(product: {0})", baseProduct));
if (baseProduct != null) {
handleProductConnected(baseProduct);
}
}
...
private void handleProductConnected(#NonNull final BaseProduct baseProduct) {
Logger.d(MessageFormat.format("Is null? {0}", baseProduct == null ? "Yes" : "No"));
Logger.d(MessageFormat.format("Type: {0}", baseProduct.getClass().getSimpleName()));
onConnectToUavFinishedSignal.countDown();
}
...
}
Problem
The code above is what I tried to do, but it's not working and guess it's because I'm misunderstanding the use of the onProductChange() and onProductConnect() methods.
The DJISDKManager.getInstance().getProduct() is always returning null.
OBS: It's always returning null immediately after the onConnectToUavFinishedSignal.await() call finishes. After a few seconds, I get a valid instance of the aircraft.
Something I've also noticed is that sometimes the onProductChange() is called with some value that the log outputs as Unknwoun and None. What are those and how can I test for them? Like if (baseProduct == ???) doSomething()
Environment
Android 9
MSDK 4.13.1
Phantom 4 Pro
Difference
According to the SDK Docs onProductChanged is primarily used to detect when the connection status changes from only remote controller connected to a full connection between the aircraft and the SDK running on your device.
Keep in mind that when the aircraft is disconnected, this method will be called with an instance of an aircraft, but this instance will come with property isConnected as false. If you print the aircraft object to the console you will notice that if isConnected is true, it will print the aircraft name, otherwise, it will print "None".
As long for the onProductConnect, it will be called always after DJISDKManager.getInstance().registerApp() succeeded or after you manually connect to the aircraft with success using DJISDKManager.getInstance().startConnectionToProduct(). In my tests, even though the app registration succeeds, the method will return false, so you might need to check if the SDKManagerCallback::onRegister results in DJISDKError.REGISTRATION_SUCCESS.
Solution
You need to listen to component change events. Unfortunately just because the product is connected it does not mean that the individual components, such as the flight controller, camera etc are connected. You will need to implement onComponentChange and add a listener to detect when a component is connected. These don't always connect in the same order and may start to connect before or after the product is connected.
#Override
public void onComponentChange(
BaseProduct.ComponentKey componentKey,
BaseComponent oldBaseComponent,
BaseComponent newBaseComponent
) {
newBaseComponent.setComponentListener(isConnected -> {
// check if component connected and access data
if (isConnected) {
if(componentKey == ComponentKey.FLIGHT_CONTROLLER) {
// DJISDKManager.getInstance().getProduct() should no longer be null
DJISDKManager.getInstance().getProduct().getModel();
}
}
})
}
I am attempting to write a Reactive Stream based on the following information:
We have a stream of Entity Events where each Event contains the ID of its Entity and a Type of either INTENT or COMMIT. It is assumed that a COMMIT with a given ID will always be preceded by one-or-more INTENTs with the same ID. When an INTENT is received, it should be grouped by its ID and a "buffer" for that group should be opened. The buffer should be "closed" when a COMMIT for the same group is received or a configured timeout has lapsed. The resulting buffers should be emitted.
Note that it is possible to receive multiple INTENTs before receiving a closing COMMIT. (Edit:) The bufferDuration should guarantee that any "opened" buffer is emitted after bufferDuration time has lapsed since the INTENT that opened the buffer was received, with or without a COMMIT.
My latest attempt at this is the following:
public EntityEventBufferFactory {
private final Duration bufferDuration;
public EntityEventBufferFactory(Duration bufferDuration) {
this.bufferDuration = bufferDuration;
}
public Flux<List<EntityEvent>> createGroupBufferFlux(Flux<EntityEvent> eventFlux) {
return eventFlux.groupBy(EntityEvent::getId)
.map(groupedFlux -> createGroupBuffer(groupedFlux))
.flatMap(Function.identity());
}
protected Flux<List<EntityEvent>> createGroupBuffer(Flux<EntityEvent> groupFlux) {
return groupFlux.publish().buffer(groupFlux.filter(this::shouldOpenBufferOnEvent), createGroupBufferCloseSelector(groupFlux));
}
protected Function<EntityEvent, Publisher<EntityEvent>> createGroupBufferCloseSelector(Flux<EntityEvent> groupFlux) {
return event -> Flux.firstEmitting(Flux.just(event).delay(bufferDuration), groupFlux.filter(this::shouldCloseBufferOnEvent).publish());
}
protected boolean shouldOpenBufferOnEvent(EntityEvent entityEvent) {
return entityEvent.getEventType() == EventType.INTENT;
}
protected boolean shouldCloseBufferOnEvent(EntityEvent entityEvent) {
return entityEvent.getEventType() == EventType.COMMIT;
}
}
And here is the test I am attempting to get passing:
#Test
public void entityEventsCanBeBuffered() throws Exception {
FluxProcessor<EntityEvent, EntityEvent> eventQueue = UnicastProcessor.create();
Duration bufferDuration = Duration.ofMillis(250);
Flux<List<EntityEvent>> bufferFlux = new EntityEventBufferFactory(bufferDuration).createGroupBufferFlux(eventQueue);
bufferFactory.setBufferDuration(bufferDuration);
List<List<EntityEvent>> buffers = new ArrayList<>();
bufferFlux.subscribe(buffers::add);
EntityEvent intent = new EntityEvent();
intent.setId("SOME_ID");
intent.setEventType(EventType.INTENT);
EntityEvent commit = new EntityEvent();
commit.setId(intent.getId());
commit.setEventType(EventType.COMMIT);
eventQueue.onNext(intent);
eventQueue.onNext(commit);
eventQueue.onNext(intent);
eventQueue.onNext(commit);
Thread.sleep(500);
assertEquals(2, buffers.size());
assertFalse(buffers.get(0).isEmpty());
assertFalse(buffers.get(1).isEmpty());
}
With this test, I get two emitted buffers, but they are both empty. You'll note that after digging around, I had to add .publish() at certain points to not get an Exception from Reactor saying This processor allows only a single Subscriber. The answer to this question, RxJava: "java.lang.IllegalStateException: Only one subscriber allowed!", is what led me to that approach.
I'm currently using Reactor, but I think this translates 1-to-1 with RxJava using Observable and methods of the same names.
Any thoughts?
I think that is the definitive use case of Rx groupBy. From the documentation:
Groups the items emitted by a Publisher according to a specified criterion, and emits these grouped items as GroupedFlowables. The emitted GroupedPublisher allows only a single Subscriber during its lifetime and if this Subscriber cancels before the source terminates, the next emission by the source having the same key will trigger a new GroupedPublisher emission.
In your case, this criterion is the ID, and on each GroupedPublisher emitted you takeUntil the type is COMMIT:
source
.groupBy(EntityEvent::getId)
.flatMap(group ->
group
.takeUntil(Flowable.timer(10,TimeUnit.SECONDS))
.takeUntil(this::shouldCloseBufferOnEvent)
.toList())
Edit: added time condition.
Thank you to Tassos Bassoukos for the input. The following Reactor code works for me:
public EntityEventBufferFactory {
private final Duration bufferDuration;
public EntityEventBufferFactory(Duration bufferDuration) {
this.bufferDuration = bufferDuration;
}
#Override
public Flux<List<EntityEvent>> create(Flux<EntityEvent> eventFlux) {
return eventFlux.groupBy(EntityEvent::getId)
.map(this::createGroupBuffer)
.flatMap(Function.identity());
}
protected Mono<List<EntityEvent>> createGroupBuffer(Flux<EntityEvent> groupFlux) {
return groupFlux.take(bufferDuration)
.takeUntil(this::shouldCloseBufferOnEvent)
.collectList();
}
protected boolean shouldCloseBufferOnEvent(EntityEvent EntityEvent) {
return EntityEvent.getEventType() == EventType.COMMIT;
}
}
I am novice to Akka and I will be very glad is someone with Akka experience could help me. I read following article http://doc.akka.io/docs/akka/2.4.1/java/untyped-actors.html and part with title "Lifecycle Monitoring aka DeathWatch" contains following example:
public class WatchActor extends UntypedActor {
final ActorRef child = this.getContext().actorOf(Props.empty(), "child");
{
this.getContext().watch(child); // <-- the only call needed for registration
}
ActorRef lastSender = getContext().system().deadLetters();
#Override
public void onReceive(Object message) {
if (message.equals("kill")) {
getContext().stop(child);
lastSender = getSender();
} else if (message instanceof Terminated) {
final Terminated t = (Terminated) message;
if (t.getActor() == child) {
lastSender.tell("finished", getSelf());
}
} else {
unhandled(message);
}
}
}
For me is not clear why we could save sender ref in actor field lastSender. For example we could have following situation: actor A send kill message to WatchActor, we save in WatchActor lastSender field reference of actor A, then actor B send kill message to WatchActor, we save in WatchActor lastSender field reference of actor B, Watch actor receiver Terminated message from child actor and answer to actor B, but actor A will not receive answer. Is this code incorrect?
What you describe could happen is correct, so the example is not entirely safe and could have unexpected outcome if multiple actors would send the "kill" message to WatchActor. The example in the docs could probably be improved in this regard.
It is annoying but maybe not super relevant to the section of the docs, which essentially describes how an actor will receive Terminated once another actor it watches has been stopped and that it happens asynchronously.
Please file a ticket here if you think it is important enough: https://github.com/akka/akka/issues
I have a problem with the waiting requests functionality in the volley library. The debugging led me to the AbstractQueue class in java.util where an element is being added (according to some values in the method that indicate a successful addition to the queue) and simultaneously - not being added(according to the 0 elements in the queue - that don't change their value). The adding method is synchronized. Bellow you can find a detailed description of the situation and my research so far. I will be really thankful if you have a look at them and share if you have any idea what is happening.
I try to automatically retry requests upon any kind of error ( for example - when there is no connection, or the server name is not correct ).
The error handler of a request adds the request back to the static singleton RequestQueue of my app.
RetriableRequestWraper.java
m_request = new StringRequest(
method,
url,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
handleResponse(response);
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError volleyError) {
handleError(volleyError);
}
});
public void handleError(VolleyError volleyError)
{
Log.d("Request: ", m_request.toString());
Log.d("handleError: ", volleyError.toString());
if(retriesCount<3)
{
executeRequest();
++retriesCount;
}
else
{
retriesCount = 0;
}
}
public void executeRequest()
{
RequestsManager.getInstance().executeRequest(m_request);
}
public void executeRequest(Request request)
{
Log.d("executeRequest ","m_requestQueue.add(request)");
m_requestQueue.add(request);
}
RequestManager.java
public void executeRequest(Request request)
{
Log.d("executeRequest ","m_requestQueue.add(request)");
m_requestQueue.add(request);
}
This approach doesn't work and when debugging inside the volley library I come to the point where the request could not be added to the mCacheQueue of the RequestQueue class, because the cacheKey of the reuqest is present in the mWaitingRequests Map. So the request is added in the queue in mWaitingRequests map, corresponding to its key. When the previous request is finished - the new one is not added to the queue although these lines are being executed in the RequestQueue class:
synchronized(this.mWaitingRequests) {
String cacheKey1 = request.getCacheKey();
Queue waitingRequests1 = (Queue)this.mWaitingRequests.remove(cacheKey1);
if(waitingRequests1 != null) {
if(VolleyLog.DEBUG) {
VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.", new Object[]{Integer.valueOf(waitingRequests1.size()), cacheKey1});
}
this.mCacheQueue.addAll(waitingRequests1);
}
}
When debugging further this line
this.mCacheQueue.addAll(waitingRequests1);
In the AbstractQueue.java (class in java.util ) the element is being added to the queue, the "modified" value is true, but throughout the hole time the "this" parameter continues to contain 0 elements.
public boolean addAll(Collection<? extends E> c) {
if (c == null)
throw new NullPointerException("c == null");
if (c == this)
throw new IllegalArgumentException("c == this");
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
Inside the offer(E e) method of PriorityBlockingQueue.java the execution of the program stops at line 453.
l452 siftUpUsingComparator(n, e, array, cmp);
l453 size = n+1;
Obviously the returned value is true, but the element is not added. My debugger could not get into the method that adds the element - siftUpUsingComparator(n, e, array, cmp);
I am going to add a timer before retrying my request, and will construct a new one. So I am not really interested in a workaround, I want to understand what and how is happening in this situation. Do you have any idea as to what could be the reason behind this?
The issue is that you try to add the same Request instance once again to the queue it has been added to. This messes up with the queue and the Request itself as it has states. For example if you simply enable markers you'll have a crash. The solution is to either just use the default retry policy or clone the requests.
I've already bound an activity to my service following this tutorial.
http://developer.android.com/guide/components/bound-services.html
I'm able to call service functions, but what if I want to for example, change some of my textviews or disable some of the toggle buttons because of work done on the service (and from the service). Would there be an easy to way to do this?
You can use messages to send information between activities and services. This is an easy way to send simple data, but may not be the best option if you need to send data very frequently, or send complicated data. This is an example of some code I have in one of my apps with a service and an activity which communicate:
Code in the activity:
//this is where you set what you want to happen
class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
//this switch reads the information in the message (usually just
//an integer) and will do something depending on which integer is sent
case 1: do_something();
case 2: do_something_2(); //etc.
default:
super.handleMessage(msg);
}
}
}
final Messenger myMessenger = new Messenger(new IncomingHandler());
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
myService = new Messenger(service);
myCallbackText = (TextView)findViewById(R.id.tv01); //This is a text view which will display status information as needed
myCallbackText.setText("Attached.");
try {
Message msg = Message.obtain(null,
1);
msg.replyTo = mMessenger; //here we send an instance of our messenger implementation as the replyTo address
mService.send(msg);
msg = Message.obtain(null,
3, this.hashCode(), 0);
mService.send(msg); //send a message with the value "3"
} catch (RemoteException e) {
//nothing you can do if the server isn't active
}
Toast.makeText(Service_testActivity.this, R.string.remote_service_connected,
Toast.LENGTH_SHORT).show();//confirmation that the connection happened successfully
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null;
mCallbackText = (TextView)findViewById(R.id.tv01);//same textview as before
mCallbackText.setText("Disconnected.");
Toast.makeText(Service_testActivity.this, R.string.remote_service_disconnected,
Toast.LENGTH_SHORT).show();
}
};
Code in the service:
In the service, you will want to have code (very similar to the code in the activity) to receive a message and save the msg.replyTo field as a Messenger object. There is an example somewhere which will have you make an object and then use an IncomingHandler like this:
ArrayList<Messenger> mClients = new ArrayList<Messenger>();
class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REGISTER_CLIENT:
mClients.add(msg.replyTo);
break;
case MSG_UNREGISTER_CLIENT:
mClients.remove(msg.replyTo);
break;
default:
super.handleMessage(msg);
}
}
}
This can allow your service to keep track of multiple clients at once and send messages to specified clients. To send a message simply use something like this:
mClients.get(1).send(Message.obtain(null, 3, new Random().nextInt(), 0));
//sends a message to the first client saved in the list