Skip to content
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

Add DcJsonrpcInstance class #2479

Merged
merged 25 commits into from
May 5, 2023
Merged

Add DcJsonrpcInstance class #2479

merged 25 commits into from
May 5, 2023

Conversation

link2xt
Copy link
Contributor

@link2xt link2xt commented Feb 28, 2023

TODO:

  • Parse responses.
  • Dispatch responses via SettableFuture.

The purpose of this is to make it possible to use JSON-RPC calls when the corresponding CFFI is not ready yet, or skip adding CFFI for new calls completely.

This change enables jsonrpc core feature in ndk-make.sh.

Since core PR deltachat/deltachat-core-rust#4341 is merged, we don't have to switch event processing to use JSON-RPC when we create a JSON-RPC instance. So we can keep event loop using the C API and use JSON-RPC for something simple at first.

For example, we can try to use it for QR-code scanning first to get proper errors on failure. Another option is to use it for event reception, so we receive proper JSON events instead of data1, data2 etc. Another option is to use it instead of the recently added in #2539 dc_http_response_t API.

Java API for JSON-RPC instance is ready, but we need a way to dispatch . ~
java.util.concurrent.CompletableFuture(UPDATE: not compatible with Android API 16) looks like something similar to the asyncio.Future we use in the Python JSON-RPC client.

@link2xt link2xt force-pushed the link2xt/jsonrpc branch 3 times, most recently from f70c7f5 to 95ea5cd Compare February 28, 2023 15:47
@r10s
Copy link
Member

r10s commented Feb 28, 2023

why is that needed currently? that would also require core to be compiled with jsonrpc, right? (i see it is a draft, but still :)

@link2xt
Copy link
Contributor Author

link2xt commented Feb 28, 2023

This PR enables jsonrpc feature in ndk-make.sh already.

It is not needed currently, but there are some ideas to start using JSON-RPC on all platforms. One suggestion is to try using it for QR-code scanning first to get proper errors on failure. Another option is to use it for event reception, so we receive proper JSON events instead of data1, data2 etc.

@github-actions
Copy link

To test the changes in this pull request, install this apk:
📦 app-preview.apk

@r10s
Copy link
Member

r10s commented Mar 1, 2023

tbh, i am unsure about getting jsonrpc in that quickly - i have the feeling it is not yet settled and stable - i see quite some breaking changes and and lots of pr in core, maybe it is wise to postpose that a little longer.

for qr code scanning, i think it is easily doable to have a "classic" ffi (in fact, we already did that at deltachat/deltachat-core-rust#3489 )

still interesting to test in a pr, however, to see how much apk grows (if it is only a few kb, the decision may be easier to have both for some time)

also, i'd like to run the benchmark script of @Hocuri at some point and see how much performance changes by using jsonrpc , eg. for the chatlist (that would need some more refactoring, of course)

but again, i do not sth of that as urgent things - but why is that combined with Nix changes then?

@link2xt
Copy link
Contributor Author

link2xt commented Mar 1, 2023

but why is that combined with Nix changes then?

Nix is not related to JSON-RPC, I just use it for my development environment. Previously I used podman but it is not really suitable for development, only for building.

@r10s
Copy link
Member

r10s commented Mar 1, 2023

a yes, i noticed too late that #2483 is actually on master and just shown wrongly as "closed" on github.

so, yes, makes sense to try jsonrpc to get some findings and impressions, and once really needed we can merge that to master. i am still sceptical to make changes to the event system on master these days, without real need, however :)

@hpk42
Copy link
Contributor

hpk42 commented Mar 2, 2023 via email

@link2xt
Copy link
Contributor Author

link2xt commented Mar 2, 2023

Currently JSON-RPC instance takes over the event channel, it consumes all events and transforms them into JSON-RPC notifications. We could modify the core to have an option to disable it, but I think it is more interesting to start with using JSON-RPC for events, so we can use rich event structures with meaningful variable names rather than text1 and text2.

EDIT 2023-04-22: JSON-RPC instance consuming events is going to be removed in deltachat/deltachat-core-rust#4341

@link2xt link2xt force-pushed the link2xt/jsonrpc branch 3 times, most recently from 13b3329 to cff98f6 Compare April 22, 2023 15:51
@github-actions
Copy link

To test the changes in this pull request, install this apk:
📦 app-preview.apk

Printing JSON-RPC responses for now.
@github-actions
Copy link

To test the changes in this pull request, install this apk:
📦 app-preview.apk

@link2xt
Copy link
Contributor Author

link2xt commented Apr 22, 2023

Current state: spams logcat with

04-22 23:00:08.775 14723 22178 I ApplicationContext: Sending request: {"jsonrpc":"2.0","method":"sleep","params":[5.0],"id":1}
04-22 23:00:08.776 14723 22178 I ApplicationContext: Got JSON-RPC response: {"jsonrpc":"2.0","id":null,"error":{"code":-32700,"message":"expected value at line 1 column 1"}}
04-22 23:00:08.776 14723 22178 I ApplicationContext: Sending request: {"jsonrpc":"2.0","method":"sleep","params":[5.0],"id":1}
04-22 23:00:08.776 14723 22178 I ApplicationContext: Got JSON-RPC response: {"jsonrpc":"2.0","id":null,"error":{"code":-32700,"message":"data did not match any variant of untagged enum Message"}}
04-22 23:00:08.777 14723 22178 I ApplicationContext: Sending request: {"jsonrpc":"2.0","method":"sleep","params":[5.0],"id":1}
04-22 23:00:08.777 14723 22178 I ApplicationContext: Got JSON-RPC response: {"jsonrpc":"2.0","id":null,"error":{"code":-32700,"message":"expected value at line 1 column 1"}}
04-22 23:00:08.778 14723 22178 I ApplicationContext: Sending request: {"jsonrpc":"2.0","method":"sleep","params":[5.0],"id":1}
04-22 23:00:08.778 14723 22178 I ApplicationContext: Got JSON-RPC response: {"jsonrpc":"2.0","id":null,"error":{"code":-32700,"message":"data did not match any variant of untagged enum Message"}}
04-22 23:00:08.779 14723 22178 I ApplicationContext: Sending request: {"jsonrpc":"2.0","method":"sleep","params":[5.0],"id":1}
04-22 23:00:08.779 14723 22178 I ApplicationContext: Got JSON-RPC response: {"jsonrpc":"2.0","id":null,"error":{"code":-32700,"message":"expected value at line 1 column 1"}}
04-22 23:00:08.779 14723 22178 I ApplicationContext: Sending request: {"jsonrpc":"2.0","method":"sleep","params":[5.0],"id":1}

@github-actions
Copy link

To test the changes in this pull request, install this apk:
📦 app-preview.apk

Copy link
Contributor Author

@link2xt link2xt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but APIs are currently unused, would be nice to use at least one of them in this PR as an example, e.g. addAccount

src/com/b44t/messenger/rpc/Rpc.java Outdated Show resolved Hide resolved
src/com/b44t/messenger/rpc/Rpc.java Outdated Show resolved Hide resolved
}
}

public int addAccount() throws RpcError {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this new API unused? Maybe use at least addAccount as an example of JSON-RPC usage?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes it is unused, I just add it as example so when adding new API the dev knows how to add them, I could try using addAccount()

@link2xt link2xt requested a review from r10s April 27, 2023 11:37
@link2xt link2xt marked this pull request as ready for review April 27, 2023 11:37
@github-actions
Copy link

To test the changes in this pull request, install this apk:
📦 app-preview.apk

Copy link
Collaborator

@Hocuri Hocuri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if we should do some measurements before merging this (how much bigger does the APK get? How much longer does a native call take now that we have to serialize and deserialize things? How much does stuff like dc_get_http_response get faster since we can return the result as JSON instead of needing multiple follow-up calls like dc_http_response_get_mimetype?).

src/com/b44t/messenger/rpc/Rpc.java Outdated Show resolved Hide resolved
src/com/b44t/messenger/rpc/Rpc.java Outdated Show resolved Hide resolved
src/com/b44t/messenger/rpc/Rpc.java Outdated Show resolved Hide resolved
id = ++requestId;
}
String jsonRequest = gson.toJson(new Request(method, params, id));
CompletableFuture<JsonElement> future = new CompletableFuture<>();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Android Studio complains that CompletableFuture requires API level 24 (current min is 16; also see apilevels.com).

Did you test this on Android 6 and below yet?

BTW, there is a SettableFuture implementation in DC, taken from the Signal code, maybe it can be used here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yikes I was using SettableFuture at first, but changed to CompletableFuture because was nicer and in the standard lib, so that is why we had that SettableFuture, will change it back, thanks a lot!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe document this in the SettableFuture then? It is indeed "Added in API level 24" on https://developer.android.com/reference/java/util/concurrent/CompletableFuture, this page can be linked from there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another question is, why doesn't our CI complain or fail? If Android Studio knows this, it should be possible to detect this automatically from gradle build.

src/com/b44t/messenger/rpc/Rpc.java Outdated Show resolved Hide resolved
String jsonResponse = dcJsonrpcInstance.getNextResponse();
Response response = gson.fromJson(jsonResponse, Response.class);

if (response.id == 0) { // Got JSON-RPC notification, ignore
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my understanding, what's a JSON-RPC notification?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some legacy thing I think, in the past the RPC used to send unsolicited notifications with events so "notifications" here means incoming events that come without the client requesting them to the RPC server, we now have a new api to manually poll for events

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in theory that check should never be true, I guess it was more about catching bugs during the creation of this PR by @link2xt and probably can be removed, maybe there is still a way to enable that notifications/events and that is why this check exist but in practice it should never be enabled

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to either replace notification with event or remove this block.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://www.jsonrpc.org/specification#notification says "A Notification is a Request object without an "id" member."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no such thing as "event" in the specification. We previously used notifications to send events from the server to the client, but JSON-RPC 2.0 specification is written in a way assuming that only the client sends requests (including notifications, i.e. requests without an ID).

src/com/b44t/messenger/rpc/RpcError.java Outdated Show resolved Hide resolved
Comment on lines 38 to +39
implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.1' // used as JSON library
implementation 'com.google.code.gson:gson:2.10.1' // used as JSON library
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, so we need one more JSON library? jackson-databind doesn't work? Can we use gson instead of jackson-databind, in order to not increase the number of dependencies?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you grep for gson, you will find that we already use it in some part of the code, it seems it is a dependency of some of our dependencies already, I had to explicitly add this because it seems the version used by the dependency was old and didn't have a needed method.

in a follow-up PR we should try to only use one of gson, jackson and org.json (the 3 are used at the moment in existing code besides this PR)

Comment on lines 35 to 41
if (response.error != null) {
future.completeExceptionally(new RpcError(response.error.toString()));
} else if (response.result != null) {
future.complete(response.result);
} else {
future.completeExceptionally(new RpcError("Got JSON-RPC response witout result or error: " + jsonResponse));
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of code changing exceptions from one form to another here, what if we replaced this whole block with future.complete(response);, used CompletableFuture<Response> instead of CompletableFuture<JsonElement>, and let the callers (like getResult()) extract the error on their own.

Also, creating a Throwable is always expensive, since it captures a stack trace. Idk how expensive excactly and if it might be noticeable in our code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all the callers will want to extract the error/result so it would be too much of a burden and repeated code, they have to do this anyway, and if there is an error they will want to print it in the logs, because it will be a programming error (ex. bad usage of API)

Copy link
Member

@adbenitez adbenitez Apr 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(these are not incoming events that could become a bunch of errors, they are only responses to API calls, so if you call a function and it throws an exception you need to handle it anyway)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But checking for response.result != null is the same amount of code as catching the error

src/com/b44t/messenger/rpc/Rpc.java Outdated Show resolved Hide resolved
adbenitez and others added 2 commits April 27, 2023 13:19
@github-actions
Copy link

To test the changes in this pull request, install this apk:
📦 app-preview.apk

@adbenitez adbenitez requested a review from Hocuri April 27, 2023 18:51
@github-actions
Copy link

To test the changes in this pull request, install this apk:
📦 app-preview.apk

@r10s
Copy link
Member

r10s commented Apr 27, 2023

I'm wondering if we should do some measurements before merging this (how much bigger does the APK get? How much longer does a native call take now that we have to serialize and deserialize things? How much does stuff like dc_get_http_response get faster since we can return the result as JSON instead of needing multiple follow-up calls like dc_http_response_get_mimetype?).

yeah, that'd be interesting, however, in general, for non-time-critical things, jsonrpc probably makes sense anyway.

dc_get_http_response would be a perfect example to test that out in this pr, btw - it is not time critical, not overall too critical, has quite some overhead with ffi

for adding accounts, not sure, i would not touch that for now.

also, other than stated at deltachat/deltachat-ios#1847 "the aim is to start by using JSON-RPC for any new API" - i am not sure if that is really the aim for now. eg. if there comes a new method to dc_msg_t, it probably makes sense to add that to ffi still. also, for reading eg. a single record, it has to be proven if that is faster or much easier i'd say.

i see this jsonprc more as something that may sneak in, if it turns out that things are easier and faster with that, this is the way automatically. iirc, jsonrpc could also get a bit more mature before being used everywhere, iirc there is no mapping between ffi and jsonrpc, low-level things and documentation are also missing. also from that perspective, there is no need to hurry.

@adbenitez
Copy link
Member

dc_get_http_response would be a perfect example to test that out in this pr, btw - it is not time critical, not overall too critical, has quite some overhead with ffi

ok, done ☑️

@github-actions
Copy link

github-actions bot commented May 1, 2023

To test the changes in this pull request, install this apk:
📦 app-preview.apk

@adbenitez
Copy link
Member

adbenitez commented May 1, 2023

also, other than stated at deltachat/deltachat-ios#1847 "the aim is to start by using JSON-RPC for any new API" - i am not sure if that is really the aim for now. eg. if there comes a new method to dc_msg_t, it probably makes sense to add that to ffi still.

yip for something like a new method to dc_msg_t wouldn't make sense, mean it like for new classes etc.

ideally at some point there will be automatic generation of the bindings then we can make more heavy migration

@adbenitez adbenitez merged commit 4461358 into master May 5, 2023
@adbenitez adbenitez deleted the link2xt/jsonrpc branch May 5, 2023 15:57
@adbenitez adbenitez restored the link2xt/jsonrpc branch May 5, 2023 15:58
@adbenitez adbenitez deleted the link2xt/jsonrpc branch May 5, 2023 15:58
@r10s
Copy link
Member

r10s commented Jun 7, 2023

ftr, as the question of size popped up above: with 1.37, we have some measurements:

android1.37 is 11.6 mb larger than android1.36 (+24%) :/
checked the reasons a bit:
plus 6.8 mb for jsonrpc in core (tested by leaving out "--features jsonrpc")
plus 4.7 mb other core growings - updated dependecies? idk (tested by compiling 1.37 against core112.8
plus <0.1 mb growing java code
i find that pretty surprisingly, compared to other features we added recently (iroh) -- maybe some dependencies are doubled by jsonrpc or so?

@r10s
Copy link
Member

r10s commented Jun 8, 2023

I'm wondering if we should do some measurements before merging this

ftr, there are some measurements at deltachat/deltachat-core-rust#4463 now

r10s added a commit that referenced this pull request Sep 25, 2023
r10s added a commit that referenced this pull request Sep 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants