[DO NOT MERGE] Simplified API - #128
Conversation
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #128 +/- ##
==========================================
- Coverage 92.09% 91.14% -0.96%
==========================================
Files 7 7
Lines 645 745 +100
==========================================
+ Hits 594 679 +85
- Misses 51 66 +15 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
I have spent some time thinking about the naming of operations on the handle type and realized the key issue is that I originally wanted to make it clear how the simple API wraps the inner API. For example, if a method on I've outlined all operations on the handle object in the table below.
For some of these operations, we could either overload functions in Some notes:
I'm also wondering about the safety of checking |
|
I added some examples of the lifecycles of different types of calls. Basic examples are in the auto-generated docstrings at the end of |
|
@csvance I think this is at the point where all functionality (except trival things like forwarding keywords) is implemented. Remaining work looks straightforward to me (to-do-list in description updated), but for example the level of documentation depends on to what extent this can be considered a replacement or not as we discussed previously. I would appreciate if you could have a look on the overall design decisions before I go too far. As an update on previous comments, I took a closer look at how to safely poll the status of |
|
@johroj we will push out 1.1.0 so people have the improved stability / cancellation without having to work from [sources]. Once I have that registered I'm going to take a look at this and getting it refactored. We can then do a 1.2.0 release sometime in the next week or so. I think its justified to have two different minor releases, one that basically fixed almost all historical stability problems and made streaming work on all supported julia versions, and one that significantly improves the interface. |
| end | ||
| PB.default_values(::Type{TestResponse}) = (; data = Vector{UInt64}()) | ||
| PB.field_numbers(::Type{TestResponse}) = (; data = 1) | ||
| PB.default_values(::Type{TestResponse}) = (;data = Vector{UInt64}()) |
There was a problem hiding this comment.
I think this is just about ProtoBuf.jl not generating files with Runic formatting.
| end | ||
| end | ||
|
|
||
| Base.isopen(req::gRPCRequest) = !(@atomic req.ready.set) # TODO dont rely on fieldnames of Base.Event? |
There was a problem hiding this comment.
I think it would be good to have some better mechanic to poll the status of the request, acquiring the full lock would be too inefficient. If we want to check private fields of Base.Event, would it be ok to add an atomic field req.isdone or similar? Or can you see some other way forward?
Same thing regarding checking if req has an exception. Could make sense to have a helper function in Curl.jl.
| # Until julia gets a dedicated syntax for importing from parent module without | ||
| # knowing its name, we need to use `parentmodule`. Otherwise the generated file | ||
| # will only work if included from the correct generated toplevel package file. | ||
| push!(import_mod_list, "const $(modname)::Module = Base.parentmodule($service_name).$(modname)") |
There was a problem hiding this comment.
We could enforce the better syntax import ..A: B if we require that protojl runs with always_use_modules = true and users always include the top-level package. But I noticed that this is not used in e.g. the unit tests of gRPCClient.jl, where the _pb file is included directly. I took that as a signal that this is something users might want to do.
| const TestRequest::DataType = Base.parentmodule(TestService).TestRequest | ||
|
|
||
| # TestService.TestRPC | ||
| function TestRPC(chan::gRPCClient.gRPCChannel, req::TestRequest; kws...) |
There was a problem hiding this comment.
The following now works:
- We can tab-complete
TestService.to see that these functions are available. And since TestService is abaremodule, there are no other functions than rpcs (evalandincludeare there too if just using amodule). - If typing
TestService.TestRPC(chan,, both VS Code and the REPL will give suggestions for providing aTestRequestas second argument. TestService.TestRPChas a docstring with syntax, usage examples etc.
| host::String | ||
| port::Int | ||
| grpc::gRPCCURL | ||
| function gRPCChannel(host::AbstractString, port::Integer; grpc = gRPCCURL()) |
There was a problem hiding this comment.
I think we should change grpc = gRPCCURL() to just use grpc_global_handle() as default instead. Setting up a new gRPCCURL is not cheap, the benchmarks did not look good until ensured a new instance is not spawned in each workload.
Thanks, then we can give these interface changes the time it takes. I added some comments about behavior where I'm still not certain what the best choice is. You may of course find more things. TODO-list in description is updated. |
Ok, this is a basic draft of what was discussed in #122
Codegen
This feels pretty much like what I had in mind. Some generic function signatures are written explicitly, with type assertions for readability. Some types are left abstract to leave room for future changes. All valid usecases (unary sync, unary async, unary channel and streams) are supported. You can see the new code in the updated
test_pb.jl.Internal logic
The API called directly from the generated code should probably be considered public, but does not need to be exported. The logic is highly based on the traits defined in the generated code - I found this easy to work with and meant that all properties of an RPC could be found by using the function type
typeof(MyService.MyRPC)as a single type parameter.The handle type
For all asynchronous calls, a handle is returned, with types depending on the kind of RPC(
gRPCUnaryHandle,gRPCStreamResponseHandle) etc. This should be a single object with methods for all operations one may need after opening a request. I'm still a bit hesitant on the current names, but the following functionality is necessary:put!will block (isfull, not available in 1.10)put!(or equivalent).isready).fetch).take!).kill)close).isopen)The approach I was going for was to overload methods from
Baseto make operation as similar to a channel as possible. This is good because it allows using short simple names without cluttering the namespace. But there are some cases where there is no clear choice, for exampleisopenand could very well refer to both the response channel or thegRPCRequest. Same problem withwait. If you have any thoughts, please let me know.Remaining items:
RenamingDespite the similarity togRPCChannelBase.Channel, I now lean towards channel being the correct term with gRPC terminology, so we should keep this anyway.Should we always throw errors from theYes, this does not seem to affect performance whatsoever.gRPCRequestwhen doingput!ortake!on the handle?gRPCConnectionOptions. AgRPCChannelshould be able to carry default options as well.gRPCClientUtils