reactor-pool icon indicating copy to clipboard operation
reactor-pool copied to clipboard

Add a `PriorityQueue`-for-idle-resources variant

Open simonbasle opened this issue 4 years ago • 1 comments

Motivation

An extension of #87 where other criteria than Least|Most-Recently-Used would be possible for polling order of idle resources (ie. created oldest).

The Priority Queue opens up more usages since a Comparator<PooledRef> can be used. For instance, one could prioritize youngest/oldest resources (in terms of creation time, independently of when it was last released) or by number of acquisitions. As long as the "priority" is not changing after insertion.

This implies:

  • Adding accessors for the PooledRef release timestamp (and creation timestamp): as "age" is a function of time and so is too dynamic for a priority => a timestamp is more absolute
  • Possible performance loss: the PriorityQueue interface relying on Comparator, implementations may be less efficient than simple baked-in fifo/lifo in Deque implementations

At the same time, it would better support maintaining order when polling-and-reinserting, which could help recover from some CAS failures.

Desired solution

An efficient Priority Queue implementation would be necessary, but the JDK only provides a binary-heap implementation that is not thread-safe (PriorityQueue) or a thread safe version that relies heavily on blocking/locks (PriorityBlockingQueue).

Considered alternatives

Implement one of the papers below.

Additional context

To our knowledge, no efficient lock-free implementation of Priority Queue exist in Java. For reference, here are a few recent papers on concurrent priority queues:

simonbasle avatar Jul 22 '20 10:07 simonbasle

ConcurrentSkipListSet might do the trick.

As the last paper in the list mentioned, it's possible to implement a priority queue using concurrent skip lists, and as it happens Java has lock-free skip list implementation.

I had the freedom to write a simple concurrent priority queue using Java's ConcurrentSkipListSet and it works well with one drawback:

The given structure is set, which requires all elements to have distinct priorities. This was bypassed by adding an additional comparator that compares timestamps if user-defined priorities are equal.

Additional: In addition to mentioned use cases, I as a user would like to define my own priorities. Use case: if the resource is in charge of processing messages (eg. Mono<Result> processMessage(Message m) ), I would like to assign priority to messages that Reactor Pool would take into consideration when choosing who will get the resource.

Example:

Mono.just(getMessage())
.flatMap(msg -> pool.withPoolable(msg.priority(), resource -> resource.processMessage(msg));

schananas avatar Sep 01 '22 16:09 schananas