Hi all,

I'm trying to build an async queue that puts consumers to sleep, piggybacking on the implementations of [JCTools](https://github.com/JCTools/JCTools/) for that.

I've also read the [article on Seastar](https://www.scylladb.com/2018/02/15/memory-barriers-seastar-linux/) mentioned here, being kindly pointed out to me on [this issue](https://github.com/JCTools/JCTools/issues/224) by Francesco N. I want to ask here as well, because I have a shoddy understand of the JMM and I want to be certain that I get the implementation right.

Question: I understand that a SPSC queue (like JCTools's `SpscArrayQueue`) needs an explicit full-fence (MFENCE) for this use-case (putting consumers to sleep), as mentioned in the article and I quote:

```
send_messages:
    push messages into the queue
    full memory barrier
    if (consumer_is_sleeping)
        wake it up
```

Question: but what about an MPMC queue?

A MPMC implies some sort of CAS-loop-based sync between the consumers and producers when the queue is empty. So for an MPMC queue (like say `concurrent.util.ConcurrentLinkedQueue`), on top of the JVM, do you still need an explicit `Unsafe.fullFence`? And if no, how can I ascertain that for any given implementation, like JCTools' `MpmcArrayQueue`, or even more complicated for a MPSC queue like JCTools' `MpscArrayQueue`?

Also, does a CAS-loop generate a full fence on its own? I have a CAS loop right after determining that I want the consumer to sleep.
Here's some code:

```java
Future<A> consumer() {
  // Step 1 (try pull)
  var a = queue.poll();
  if (a != null) {
    return Future.successful(a);
  }

  // Step 2 (register promise)
  // Does this require any more fencing or whatever?
  var ref = await.get();
  while (ref == null) {
    val p = new Promise<Unit>();
    if (await.compareAndSet(null, p))
      ref = p;
    else
      ref = await.get();
  }

  // Step 3 (try again) - could this be re-ordered with the above step?
  a = queue.poll();
  if (a != null) {
    return Future.successful(a);
  }

  // Step 4 (sleep, then retry)
  //
  // N.B. this is another CAS-loop managed by Promise ;-)
  return await.future.flatMap(u -> pull());
}
```

And then the consumer:

```java
void push(A a) {
  // Step 1 (push)
  queue.push(a);

  // Step 2 - is this absolutely needed for multi-producer queues?
  Unsafe.fullFence()

  // Step 3 - are there consumers waiting?
  final Promise<Unit> ref = await.get();

  // Step 4 — notify sleeping consumers
  if (ref != null) {
    ref.complete(unit)
  }
}
```

My issue is that I'm building a configurable queue (SPSC, MPSC, SPMC, MPMC, so all combinations are possible and the underlying implementation selects the right one). And I want to see in what situations I can avoid those explicit memory fences in case the underlying queue is multi-producer and/or multi-consumer.

To make matters more complicated I also want to put producers to sleep via the same mechanism, in case the underlying queue is limited in size (e.g. a circular array), in which case the roles are reversed, but I'm assuming it's the same thing.

--
Alexandru Nedelcu
alexn.org

--
You received this message because you are subscribed to the Google Groups 
"mechanical-sympathy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to