-
Notifications
You must be signed in to change notification settings - Fork 51
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
flux_respond(h, msg, 0, NULL) results in protocol error? #495
Comments
For better or worse, what happens is if the
Is that weird? |
I can see how that makes sense. I came upon this because the current version of the python bindings always pass a payload to be populated in order to return a response object. The interface currently has no way for the user to say whether there should or should not be a payload. I suppose I can add one, but I had assumed that the result would either be an empty json object or that |
I was thinking about this on the way home, and I think I know why this surprised me as much as it did. The payload is explicitly noted as being optional, and is in fact ignored if the error is non-0. So, if a protocol normally responds with a payload, |
Letting you know that I read the above discussion, but don't have a strong opinion one way or the other. As it sits, the onus is on the |
Without using the interface yet, I can say I agree with @trws that the current behavior seems pretty awkward and possibly violates principle of least surprise. I think it would be much improved if empty payload is allowed (setting json_ptr to NULL if provided) and will save lots of frustration for future developers... |
Is the automatic EPROTO on extra/missing payload, or the "dual function" of I feel a little guilty about both because clearly they do make simple functions more complicated, but I proposed them that way because I found myself repeating the same little chunks of code over and over, e.g. if (rc < 0) {
if (flux_respond_errnum (h, msg, errno) < 0)
flux_log_error (h, "...");
} else {
if (flux_respond (h, msg, json_str) < 0)
flux_log_error ("...");
} versus if flux_respond (h, msg, rc < 0 ? errnum : 0, json_str) < 0)
flux_log_error (h, "..."); or if (flux_response_decode (msg, &json_str) < 0)
goto done;
if (json_str == NULL) {
errno = EPROTO;
goto done;
} versus without the block testing for NULL. |
IMO automatic EPROTO on missing payload is the awkward bit here. I predict it will be surprising to 3rd party users that pass in a pointer to JSON object to get EPROTO error. It also hides the fact that the error was due to missing but presumably expected payload, vs server side EPROTO which could be due to malformed input JSON payload. To save duplicated code for in-tree users, it would make more sense to have a util wrapper in libflux-internal or macro in So far I think the |
I like the flux_respond usage overall, but I would actually prefer that always allow a payload to be either empty or full. In the case of an error, I can understand ignoring it, but it might be useful to send back a payload with further information on the reason for the failure, or application specific information to help in recovery. By the same token, I agree that a missing payload when one is expected is a potential protocol violation, but it isn't an error in the RPC protocol, rather it would be one in the user's protocol that is being defined on top of RPCs, right? Overall, I'm agreeing with @grondo here that it's the EPROTO for presence or lack of payload, but as a side issue, ignoring payloads on error code seems a bit of a missed opportunity for richer exceptions (unless there's an underlying RPC protocol issue here I'm unaware of that would make it hard). |
Well I think of the errnum as a low-level error. Your protocol on top could always define its own JSON error response that includes, say an error string. But for low level RPCs this is maybe overkill... Good comments all around though! |
Right, I was just going to say the same thing. An error response is defined in RFC 3 as numeric only. That doesn't prevent a service from defining its own structured error response, encoded as a regular response payload. Obviously these things aren't set in stone, but that one's quite a bit harder to change than the EPROTO behavior. |
It should be noted that the EPROTO change is simple but will involve touching quite a lot of code (like every request handler and every RPC user). I'm OK with doing it and soon if people agree it should change though. |
I would be for it, but why so much fallout? It would now be valid to pass NULL in places where one couldn't before, but is that a sufficiently common pattern to cause a lot of fallout, or is there breakage I just missed? |
It's the decode paths that are the problem. One would need to start checking for NULL before passing a payload to |
Could you define a new macro or libflux-internal function that wraps the old behavior, then search-replace existing users? |
Maybe something like |
Flags and/or variadic decode functions might be the way to go. Macros/internal functions might make a transition to a new idiom a bit easier. Or will variadics cause problems for bindings? |
What would you want it to be variadic for? Do you have a design in mind? The bindings tend not to like them, so I would prefer to avoid it, but if it's sufficiently useful I can always write a wrapper. This is where having c++-style default arguments would be nice. C99 macro-based default arguments might be a nice compromise if that's the case. |
I was thinking of something like // fmt: n=nodeid t=topic string j=json payload r=raw payload l=payload length
flux_rpc_getf (flux_rpc_t *rpc, const char *fmt, ...); That would allow the raw and normal interfaces to be collapsed and could provide a bit more expressivity for what the caller expects (viz a viz EPROTO). |
Now that I look at it it says "meh" to me. |
How about something like this? enum flux_rpc_flags {
FLUX_RPC_NO_FLAGS = 0,
FLUX_RPC_PAYLOAD_UNEXPECTED = 1<<0,
FLUX_RPC_PAYLOAD_RAW = 1<<1,
FLUX_RPC_PAYLOAD_REQUIRED = 1<<2,
FLUX_RPC_PAYLOAD_LENGTH = 1<<3
};
flux_rpc_getf (flux_rpc_t *rpc, int *errnum, size_t *payload_length, const char ** payload, flux_rpc_flags flags); |
errnum is mutually exclusive with payload in the RFC 3 definition of a response message. The idiom we're using is to return -1 and set errno=errnum if the remote sends one back. That's harder to change as noted above. There is a flags arg already in
If any of those are set, you get EPROTO if the response doesn't have the expected payload. If none of these flags are set, you get the behavior that you anticipated when you opened the bug? |
And maybe similar flags for |
That certainly works for me. Extra points if the |
Scratch that last. Your solution looks good, but don't make respond potentially fail because of it. Giving the client the ability to cause an error return against a service just seems like a bad idea... |
No |
This sounds good to me as well. @garlick, I'd be willing to help with grunt work if you'd like since you are already focused on the content service. |
I did see that, but didn't put 2 and 2 together yet. I will base any work I do on top of #493, as that one looks like it will be ready for merge pretty soon. |
Ok, I started looking into implementing the suggestions here and I'm quickly getting lost.
At this point I'm not sure that adding BTW, my inclination right now would be to focus on I don't feel confident enough to say this is the right way to go though. |
Here's a thought. How about the following low level changes:
At the high level (rpc, request/response encode/decode):
While that seems like a cleaner foundation to build upon, it will increase repeated code for users. We should take a look at that and see what we can do about it. |
Yes, that sounds like a pretty clever way to handle the payload/no payload thing. For duplicated code, I think some kind of convenience functions for "special" payload types may be useful. (This is what I was attempting to get at with the extensible message types I ineloquently hinted at above). We could end up back near where we are now, but with a more solid foundation and (perhaps?) a methodology for adding perhaps other payload types (even for users outside flux-core?) [maybe getting too far off track here.] |
I just happened across this issue randomly, thought it was addressed some time ago but don't see any backreferences. Can we close? |
Not fixed.
Now that we have |
@garlick, in the above do you mean to say |
I think the api has evolved a bit since this discussion began, and I'm not too sure that comment stands as the best approach. What I as thinking in the dec 21 2016 comment was that we could just relax the decode function (including flux_rpc_get) handling of optional payload pointer arg so
This would mean it falls to callers to detect missing payload before attempting to decode it. However, my point above was that some of those may productively migrate to Does that make sense? |
@garlick Sounds good! |
As an aside, yesterday I tried to begin cleanup by replacing So another fallout from this EPROTO behavior is in some scenarios |
Yeah it doesn't make much sense to call the variadic version when there is
nothing to parse. Going forward I was thinking it could continue to
return eproto if there is no payload while relaxing the non variadic
version, if that makes sense.
On Mar 3, 2017 8:48 AM, "Al Chu" <[email protected]> wrote:
As an aside, yesterday I tried to begin cleanup by replacing flux_rpc_get
(r, NULL) calls with flux_rpc_getf (r, "{ ! }"). It took me awhile to
realize this doesn't work, as internally flux_response_decode() would
always be passed a non-NULL json_str argument.
So another fallout from this EPROTO behavior is flux_rpc() cannot be paired
with flux_rpc_getf() seemlessly, atleast in this "obvious" scenario where
you know no payload is going to be returned.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#495 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAKX2791b6F4msQ-ExJxyyyBvsiZT0DIks5riERugaJpZM4G1QUr>
.
|
Yeah, I originally was trying to replace as much usage of |
Rearchitect flux_request_decode. User can now pass in a NULL or non-NULL json_str pointer. The function will succeed regardless if a payload is available or not. The function will no longer return EPROTO under a payload & pointer mismatch. User is responsible to check for NULL or non-NULL per their expectations. Fixes flux-framework#495
Rearchitect flux_request_decode. User can now pass in a NULL or non-NULL json_str pointer. The function will succeed regardless if a payload is available or not. The function will no longer return EPROTO under a payload & pointer mismatch. User is responsible to check for NULL or non-NULL per their expectations. Fixes flux-framework#495
Perhaps this is intentional, but I did not expect it. When I send a response with a null payload, the receiver gets an EPROTO from the flux_rpc_get call. Clearly I can get around this by allocating an empty JSON object and passing that in, but somehow that feels wrong...
The text was updated successfully, but these errors were encountered: