Skip to content

Add non-blocking trySend and tryReceive methods#423

Merged
adamw merged 6 commits intomasterfrom
add-try-send-receive
Mar 3, 2026
Merged

Add non-blocking trySend and tryReceive methods#423
adamw merged 6 commits intomasterfrom
add-try-send-receive

Conversation

@adamw
Copy link
Copy Markdown
Member

@adamw adamw commented Mar 2, 2026

Summary

  • Exposes jox 1.1.2's new non-blocking trySend/tryReceive methods on ox's Sink and Source traits
  • Follows the existing foo / fooOrClosed pair convention throughout
  • Adds two helpers in ChannelClosed companion to convert jox's null/sentinel return values

New API:

  • Sink.trySend(t: T): Booleantrue if sent, false if no space/receiver, throws ChannelClosedException when closed
  • Sink.trySendOrClosed(t: T): Boolean | ChannelClosed — non-throwing variant
  • Source.tryReceive(): Option[T]Some(v) if received, None if nothing immediately available, throws when closed
  • Source.tryReceiveOrClosed(): Option[T] | ChannelClosed — non-throwing variant

All methods are non-blocking (complete in bounded time) and may spuriously return false/None under contention, matching the jox semantics.

Test plan

  • trySend returns true for buffered channel with space, false when full, throws ChannelClosedException when closed
  • trySendOrClosed returns Boolean or ChannelClosed without throwing
  • tryReceive returns Some(v) when value available, None when empty, throws when closed
  • tryReceiveOrClosed returns Some(v), None, or ChannelClosed without throwing
  • tryReceiveOrClosed returns Some(v) from a done channel that still has buffered values
  • Full test suite passes (18 new tests, 899 total)

🤖 Generated with Claude Code

Expose jox 1.1.2's new non-blocking try* methods on ox's Source and
Sink traits. All four variants follow the existing orClosed convention:

- Sink.trySend(t): Boolean         — true=sent, throws when closed
- Sink.trySendOrClosed(t): Boolean | ChannelClosed
- Source.tryReceive(): Option[T]   — Some=received, None=not available, throws when closed
- Source.tryReceiveOrClosed(): Option[T] | ChannelClosed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 2, 2026 17:39
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds non-blocking trySend/tryReceive APIs to ox.channels by wiring through jox 1.1.2’s new try*OrClosed methods, keeping the existing throwing vs non-throwing method pair convention and adding test coverage.

Changes:

  • Added tryReceive / tryReceiveOrClosed to Source and trySend / trySendOrClosed to Sink.
  • Added ChannelClosed companion helpers to translate jox null/sentinel results into ox-friendly union types.
  • Added a dedicated ChannelTryTest suite covering success/empty/full/closed (done+error) cases.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
core/src/main/scala/ox/channels/Channel.scala Introduces the new trySend* and tryReceive* APIs on Sink/Source and delegates to jox.
core/src/main/scala/ox/channels/ChannelClosed.scala Adds conversion helpers for jox try*OrClosed return encodings (null/sentinel/closed).
core/src/test/scala/ox/channels/ChannelTryTest.scala Adds tests validating the new non-blocking try-send/try-receive semantics and closed-channel behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +67 to +73
def tryReceive(): Option[T] = tryReceiveOrClosed() match
case c: ChannelClosed => throw c.toThrowable
case opt: Option[T @unchecked] => opt

/** Attempt to receive a value from the channel if one is immediately available. This method never blocks or suspends the calling thread.
* Doesn't throw exceptions when the channel is closed, but returns a value.
*
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

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

tryReceive() re-implements the ChannelClosedUnion.orThrow pattern via a manual match (and needs an @unchecked type pattern). For consistency with other throwing variants in this file (e.g. receive()/send()), consider implementing this as tryReceiveOrClosed().orThrow to reduce duplicated logic and avoid the unchecked match.

Suggested change
def tryReceive(): Option[T] = tryReceiveOrClosed() match
case c: ChannelClosed => throw c.toThrowable
case opt: Option[T @unchecked] => opt
/** Attempt to receive a value from the channel if one is immediately available. This method never blocks or suspends the calling thread.
* Doesn't throw exceptions when the channel is closed, but returns a value.
*
def tryReceive(): Option[T] = tryReceiveOrClosed().orThrow
/** Attempt to receive a value from the channel if one is immediately available. This method never blocks or suspends the calling thread.
* Doesn't throw exceptions when the channel is closed, but returns a value.
*
* Doesn't throw exceptions when the channel is closed, but returns a value.
*

Copilot uses AI. Check for mistakes.
Comment on lines +167 to +170
def trySend(t: T): Boolean = trySendOrClosed(t) match
case c: ChannelClosed => throw c.toThrowable
case b: Boolean => b

Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

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

trySend() mirrors the ChannelClosedUnion.orThrow logic using a manual match. To stay consistent with other throwing helpers (send(), done(), etc.) and reduce duplication, consider implementing this as trySendOrClosed(t).orThrow.

Suggested change
def trySend(t: T): Boolean = trySendOrClosed(t) match
case c: ChannelClosed => throw c.toThrowable
case b: Boolean => b
def trySend(t: T): Boolean = trySendOrClosed(t).orThrow

Copilot uses AI. Check for mistakes.
case _: JChannelDone => Done
case e: JChannelError => Error(e.cause())
case v => Some(v.asInstanceOf[T])

Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

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

Scaladoc formatting: the type is written as `Boolean | ChannelClosed`. (note the extra trailing backtick before the period). This will render oddly in generated docs; remove the stray backtick so the inline code span is properly closed.

Suggested change
/** Converts the result of jox's `trySendOrClosed()` (which returns `null | JChannelClosed | sentinel`) to `Boolean | ChannelClosed`.
* `null` means the value was sent, any other non-ChannelClosed value (sentinel) means it was not sent.

Copilot uses AI. Check for mistakes.
adamw and others added 5 commits March 2, 2026 17:48
- trySend and tryReceive now delegate to orThrow, consistent with send/receive
- Fix stray backtick in ChannelClosed scaladoc

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove fromJoxTryReceiveOrClosed/fromJoxTrySendOrClosed helper methods
  and inline their logic in tryReceiveOrClosed/trySendOrClosed, delegating
  to the existing fromJoxOrT and fromJox methods. Expose fromJox as
  private[ox] to enable this.
- Update ChannelTryTest to use Scala 3 in: syntax (no braces)
- Fix CircuitBreakerTest flakiness by wrapping transition assertions in
  eventually{}, giving a 1s window for actor-dispatched state changes to
  propagate under JVM load

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The test relied on a non-fair semaphore giving permits to specific
elements (3 and 4) in a predictable order, which wasn't guaranteed.
With the inProgress buffer holding all forks, any of the waiting
forks could acquire the semaphore — including element 4 before
elements 1 and 2 run — causing failures.

Redesign: use exactly 2 elements with mapPar(2). Since there are
only 2 forks competing for 2 permits, both always start concurrently
with no non-determinism. Element 2 fails after 100ms, giving a 900ms
window to cancel element 1 (sleeping 1s).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@adamw adamw merged commit 892279c into master Mar 3, 2026
6 checks passed
@adamw adamw deleted the add-try-send-receive branch March 3, 2026 11:39
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.

2 participants