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

Modify Handler.finished API to remove assumption all responses are one-shot. #81

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

redvers
Copy link

@redvers redvers commented Jan 21, 2025

NOTE: This is a breaking change.

TODO:
✅ Update Docs
✅ Release Notes
✅ Clearer Example Maybe?

Caveat:

This may be a dragon. From handler.pony:

  It is guaranteed that the call sequence is always:
                                                                                   
  - exactly once:       `apply(request_n, requestid_n)`
  - zero or more times: `chunk(data, requestid_n)`                              
  - exactly once:       `finished(requestid_n)`                                    
                    
  And so on for `requestid_(n + 1)`. Only after `finished` has been called for a   
  `RequestID`, the next request will be received by the Handler instance, there will
  be no interleaving. So it is save to keep state for the given request in a Handler between calls to `apply`
  and `finished`.                        

This raises the very real possibility that a new apply() request may come in on the same Handler while we're still doing our recursive calls to finished().

In other words, "it is safe to keep state for the given request in the Handler between calls" may no longer be true.

More investigation required.

@ponylang-main ponylang-main added the discuss during sync Should be discussed during an upcoming sync label Jan 21, 2025
@SeanTAllen
Copy link
Member

@redvers I see no problem with finished getting called multiple times on the user supplied handler so long as "send finished" is only called once.

Also note, that all Handler methods take a request id. Existing code already needs to account for more than 1 request being in-flight at a time.

See:

  be send_finished(request_id: RequestID) =>
    """
    ### Verbose API

    Indicate that the response for `request_id` has been completed,
    that is, its status, headers and body have been sent.

    This will clean up resources on the session and
    might send pending pipelined responses in addition to this response.

    If this behaviour isnt called, the server might misbehave, especially
    with clients doing HTTP pipelining.
    """
    None

on session.pony.

The update to those docs you posted would be that finished is called 1 or more times. 1 time is guaranteed if you return true that you are finished, otherwise it is called until the handler indicates it is finished finishing.

@SeanTAllen SeanTAllen added the changelog - changed Automatically add "Changed" CHANGELOG entry on merge label Jan 21, 2025
@ponylang-main
Copy link
Contributor

Hi @redvers,

The changelog - changed label was added to this pull request; all PRs with a changelog label need to have release notes included as part of the PR. If you haven't added release notes already, please do.

Release notes are added by creating a uniquely named file in the .release-notes directory. We suggest you call the file 81.md to match the number of this pull request.

The basic format of the release notes (using markdown) should be:

## Title

End user description of changes, why it's important,
problems it solves etc.

If a breaking change, make sure to include 1 or more
examples what code would look like prior to this change
and how to update it to work after this change.

Thanks.

@@ -103,5 +103,5 @@ class BackendHandler is Handler
_session.send_raw(_response, request_id)
_session.send_finished(request_id)

fun ref finished(request_id: RequestID) => None
// fun ref finished(request_id: RequestID): Bool => true
Copy link
Member

Choose a reason for hiding this comment

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

why is the commented out?

@redvers
Copy link
Author

redvers commented Jan 25, 2025

Also note, that all Handler methods take a request id. Existing code already needs to account for more than 1 request being in-flight at a time.

This is the part of the documentation that made me believe that Handlers don't need to deal with multiple requests at the same time:

And so on for requestid_(n + 1). Only after finished has been called for a
RequestID, the next request will be received by the Handler instance, there
will be no interleaving. So it is save to keep state for the given request
in a Handler between calls to apply and finished.

Specifically:

there will be no interleaving.

If Handlers do need to be able to handle more than one request at a time then we likely need to clarify the documentation and modify the examples to do this.

@redvers redvers changed the title WIP: Modified finished API remove assumption all responses are one-shot. Modified finished API remove assumption all responses are one-shot. Jan 25, 2025
@redvers redvers changed the title Modified finished API remove assumption all responses are one-shot. Modify Handler.finished API to remove assumption all responses are one-shot. Jan 25, 2025
Copy link
Member

@SeanTAllen SeanTAllen left a comment

Choose a reason for hiding this comment

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

release notes should only have newlines for code formatting and to delineate paragraphs. You don't know the size of the user's viewpoint.

.release-notes/81.md Outdated Show resolved Hide resolved
.release-notes/81.md Outdated Show resolved Hide resolved
.release-notes/81.md Outdated Show resolved Hide resolved
.release-notes/81.md Outdated Show resolved Hide resolved
.release-notes/81.md Outdated Show resolved Hide resolved
.release-notes/81.md Outdated Show resolved Hide resolved
@mfelsche
Copy link
Contributor

Sorry for chiming in quite late on this PR, it somehow got lost in my notifications.

I'd rather not have the Handler.finished(request_id: RequestId) function be called multiple times by the underlying server connection. Its purpose is to signal to the handler that the final inbound request chunk is received. It should not be used to drive a state machine for sending out parts of responses. This can and should be done by other means. The chunked example in this PR can be driven by a function or behaviour calling itself after one round and changing the internal state.

Also the general logic of a Handler is, that it is spawned per HTTP session, which maps directly to a TCP Connection. I think you can expect a Handler working "sequentially" on one request at a time as they come in. I don't know if it is legal for a client to interleave chunked requests and their chunks. I would think it is not, for HTTP/1.0 and HTTP/1.1 but have to check.

If the motivation behind this PR is to send out chunked responses, we can work on a way to do this for your use case. It shouldn't be too hard, even without changes to this package.

@redvers
Copy link
Author

redvers commented Jan 28, 2025

The chunked example in this PR can be driven by a function or behaviour calling itself after one round and changing the internal state.

The problem is really this.

If you want to send something significant like the contents of a 4.7G iso file, there is no way to read a block, get it sent down the wire, let the behavior end (so the runtime can GC that datastructure), and be called again.

Incoming data is given to Handler in chunks. There is currently no corresponding way to send data out in the same way.

If there is a way to do this without changes to the package I'm all ears. Modifying the API was obviously a last resort.

@mfelsche
Copy link
Contributor

I will contribute an example for a use case like this tonight. I hope this helps you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
changelog - changed Automatically add "Changed" CHANGELOG entry on merge discuss during sync Should be discussed during an upcoming sync
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants