Skip to content

A message the parser cannot read costs that message and nothing after it - #109

Merged
sepehr-safari merged 2 commits into
mainfrom
an-unreadable-message-costs-only-itself
Sep 23, 2026
Merged

sepehr-safari merged 2 commits into
mainfrom
an-unreadable-message-costs-only-itself

Conversation

@sepehr-safari

Copy link
Copy Markdown
Contributor

Closes #108.

When parseRelayMessage failed inside receive, the error returned before the reassembly buffer was cleared. The failed message stayed in msg, every later frame was appended to it, and every message after it on that connection failed to parse as well. Bytes that are not JSON, a message type the parser has no case for, or an EVENT whose event is malformed were each enough.

What changes

  • The buffer is cleared once a message is complete, whether it parses or not.
  • A message the parser cannot read is skipped and counted, and receive reads on. Relay.unreadable() reports the count, so a caller can say how many it dropped. receive and receiveTimeout no longer return error.InvalidMessage; no caller I could find handled it by name.
  • A deadline given to receiveTimeout still holds while messages are being skipped. The deadline is otherwise noticed only when the socket runs dry, and a relay streaming messages the parser cannot read never lets it run dry, so the skip checks it.
  • Running out of memory inside the parser comes back as OutOfMemory instead of InvalidMessage, so a skip never swallows a good message.

Tests

Each of these fails with its fix removed and passes with it:

  • a message the parser cannot read costs that message and nothing after it scripts not-JSON, an unknown type and a malformed EVENT ahead of a NOTICE and an EOSE. It catches skipping without clearing, clearing without skipping, and the original clear-only-on-success.
  • skipping unreadable messages still stops at the deadline gives an already-passed deadline to a stream of unreadable messages and expects error.Timeout, then the readable message on the next call.
  • running out of memory is reported as that, not as a malformed message fails every allocation of an EVENT parse in turn and requires OutOfMemory each time, with nothing leaked. It uses a tag-heavy event, because a small one never reaches the backing allocator inside the event conversion and the inner mapping would go untested.
  • parse EVENT refuses an event naming the same field twice pins the duplicate-key refusal on the relay path, which Parse an event that carries a key NIP-01 does not name #107 only pinned on fromJson.

All 210 tests pass.

Release

The last commit bumps the version to 0.14.3 and moves both fixes, this one and #107, into the changelog.

…tion

When parseRelayMessage failed inside receive, the error returned before the reassembly buffer was cleared. The failed message stayed in it, every later frame was appended to it, and every message after it on that connection failed to parse too.

The buffer is now cleared once a message is complete, whether it parses or not, and a message that cannot be read is skipped and counted. Relay.unreadable() reports how many. A deadline given to receiveTimeout still holds while messages are being skipped, since a relay streaming them never lets the socket run dry.

Running out of memory inside the parser now comes back as OutOfMemory rather than InvalidMessage, so a skip never swallows a good message.

Closes #108.
Carries #107 and the fix on this branch: an event that carries a key NIP-01 does not name parses, and a relay message the parser cannot read costs that message only.
@sepehr-safari
sepehr-safari merged commit e805d10 into main Sep 23, 2026
2 checks passed
@sepehr-safari
sepehr-safari deleted the an-unreadable-message-costs-only-itself branch September 23, 2026 08:57
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.

One message the parser cannot read breaks every message after it on that connection

1 participant