Skip to content

std.http.listen fails (handler_failed) at ~6kib, despite larger application buffer #430

Description

@wschwab

Summary

On Zero 0.3.4, an HTTP handler begins failing when its response grows to
approximately 6 KiB.

The application constructs the complete JSON response successfully in a
caller-owned buffer. Increasing that application buffer from 14 KiB to 30 KiB
does not move the failure point.

Once the response reaches the apparent runtime boundary, the server reports
handler_failed and the HTTP client receives status 500.

This looks like a separate limit inside std.http.listen or its handler
callback machinery, rather than exhaustion of the buffer used by the
application to construct the JSON.

Environment

  • Zero: 0.3.4
  • Build: 5b3a90a
  • Host: linux-x64
  • Backend reported by zero --version --json: zero-c
  • HTTP API: std.http.listen
  • Handler signature:
fn handle(
    request: Span<u8>,
    response: MutSpan<u8>,
) -> Maybe<Span<u8>>

Application structure

The application creates a JSON roster containing an increasing number of
small records. It first constructs the body in its own caller-owned byte
buffer, then passes that completed body to the standard HTTP response helper.

The relevant structure is equivalent to:

  fn handle(
      request: Span<u8>,
      response: MutSpan<u8>,
  ) -> Maybe<Span<u8>> {
      if std.http.requestIsGet(request, "/api/foo/bar") {
          var body_storage: [14000]u8 = [0_u8; 14000]

          // The application successfully serializes the complete JSON into
          // body_storage here.
          let body: Span<u8> = build_roster_json(body_storage)

          return std.http.writeJsonResponse(response, 200_u16, body)
      }

      return std.http.writeJsonError(response, 404_u16, "NOT_FOUND")
  }

  pub fn main(world: World) -> Void raises {
      check std.http.listen(world, 9657_u16)
  }

I also tested changing the application-owned builder from:

var body_storage: [14000]u8 = [0_u8; 14000]

to:

var body_storage: [30000]u8 = [0_u8; 30000]

That did not change the observed HTTP failure boundary.

Steps to reproduce

  1. Start a Zero HTTP server using std.http.listen.
  2. Provide an endpoint that returns a JSON array.
  3. Add small objects to the array over successive requests.
  4. Request the complete array after each addition.
  5. Continue until the serialized response approaches approximately 6 KiB.

In our application, a small roster succeeds. After accumulating roughly 36
small crew summaries, the roster request fails:

curl -i http://127.0.0.1:9657/api/foo/bar

The server reports:

handler_failed

The client receives HTTP 500 instead of the valid JSON body.

The records themselves remain persisted and readable individually. Smaller
roster responses continue to work. The failure is specifically associated
with returning the larger aggregate response.

Observed result

  • The application-level JSON builder has sufficient capacity.

  • JSON construction succeeds.

  • Persisted records are intact.

  • Small responses succeed.

  • At approximately 6 KiB, the native HTTP handler fails.

  • Increasing the application builder to 30 KiB does not increase the maximum
    HTTP response size.

  • The server surfaces the failure as handler_failed / HTTP 500.

Expected result

One of the following would be expected:

  1. The handler can return any response that fits in the `response:
    by the runtime; or
  2. The size limit is documented and exposed to the handler; or
  3. The response helper returns a specific capacity/overflow failure that lets
    the application handle the condition.

A hidden lower limit that produces only handler_failed makes it difficult to
determine the supported response size or recover gracefully.

Questions

  1. What is the actual capacity of the response: MutSpan<u8> passed to an
    std.http.listen handler?
  2. Is the capacity intended to be approximately 6 KiB?
  3. Can the handler inspect that capacity using std.mem.len(response)?
  4. Is there a supported way to configure a larger HTTP response buffer?
  5. If the response is too large, could the runtime produce a more specific
    diagnostic than handler_failed?
  6. Is streaming or chunked response output planned?

Additional context

This was discovered while running a conformance suite repeatedly against the
same persisted state. A fresh run remained green because its roster was still
small. Repeated runs accumulated enough valid records to cross the response
boundary.

That initially resembled persisted-state contamination, but the data and
serialization remained valid. The failure persisted at the same approximate
response size even after substantially increasing the application's local JSON
buffer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions