effect icon indicating copy to clipboard operation
effect copied to clipboard

Queue.sliding differs in safe/unsafe behavior

Open alex-dixon opened this issue 7 months ago • 1 comments

What version of Effect is running?

3.14.18

What steps can reproduce the bug?

Not sure this is a bug but the behavior is different enough where I wanted to report it. Note this is only tested on a queue size of 1.

It looks like for unsafe offer on a sliding queue the latest value is not taken if two puts are made on the same event loop tick. I couldn't find any documentation suggesting this is expected.

https://effect.website/play/#56af029368d5

What is the expected behavior?

Either for the behavior to be documented or the same behavior from safe and unsafe versions of offer on a sliding queue

What do you see instead?

Using unsafe offer to put 1 and then 2 to the sliding queue results in 1 being processed and 2 never making it into the queue

Using safe offer to put 1 and then 2 to the sliding queue, 1 is never processed, just 2.

Both 1 and 2 are both processed when interleaving effect.sleep(0) between puts of 1 and 2

Additional information

No response

alex-dixon avatar May 04 '25 20:05 alex-dixon

This is currently expected behaviour, the unsafeOffer code is:

  unsafeOffer(value: A): boolean {
    if (MutableRef.get(this.shutdownFlag)) {
      return false
    }
    let noRemaining: boolean
    if (this.queue.length() === 0) {
      const taker = pipe(
        this.takers,
        MutableQueue.poll(MutableQueue.EmptyMutableQueue)
      )
      if (taker !== MutableQueue.EmptyMutableQueue) {
        unsafeCompleteDeferred(taker, value)
        noRemaining = true
      } else {
        noRemaining = false
      }
    } else {
      noRemaining = false
    }
    if (noRemaining) {
      return true
    }
    // Not enough takers, offer to the queue
    const succeeded = this.queue.offer(value)
    unsafeCompleteTakers(this.strategy, this.queue, this.takers)
    return succeeded
  }

which basically says "try to offer, if it doesn't succeed return false", which is what is happening in the unsafe version, at this point the queue doesn't know if it's unbounded / sliding / bounded because in current version that behaviour is decided by the backpressure strategy (which can even be custom) and in the unsafe path we can't call the handleSurplus because it is an effect that can suspend, so translated in simple terms what's happening here is:

  • in the unsafe version:
  1. push first element, success
  2. push second element, fail
  • in the safe version:
  1. push first element, success
  2. push second element, fail
  3. invoke backpressure, slide queue
  4. push second element, success

We will be able to change the behaviour in 4.0 where instead of custom backpressure stragey we will only support the classic bounded/unbounded/sliding variants, for reference the 4.0 code is: https://github.com/Effect-TS/effect-smol/blob/f0bb61c465f27f1a602248abec278cd915ccf74b/packages/effect/src/Queue.ts#L294

mikearnaldi avatar May 05 '25 07:05 mikearnaldi