This is an automated email from the ASF dual-hosted git repository.

He-Pin pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko.git


The following commit(s) were added to refs/heads/main by this push:
     new 4fab32d5c8 stream: deprecate Source.queue overloads that materialize 
SourceQueueWithComplete (#3095)
4fab32d5c8 is described below

commit 4fab32d5c83b0d8bc9a5cf486e526df59d9f69a6
Author: He-Pin(kerr) <[email protected]>
AuthorDate: Thu Jul 16 23:02:11 2026 +0800

    stream: deprecate Source.queue overloads that materialize 
SourceQueueWithComplete (#3095)
    
    Motivation:
    The Source.queue overloads returning SourceQueueWithComplete have an
    async offer future that can hang indefinitely under backpressure, causing
    OOM/FGC in production. BoundedSourceQueue is the safer alternative.
    
    Modification:
    - Deprecate the four Source.queue overloads (scaladsl + javadsl)
    - Update docs with migration table and warnings
    - Fix @apidoc references in Paradox docs
    - Suppress deprecation warnings at internal call sites
    
    Result:
    Users are guided toward BoundedSourceQueue and Source.actorRef, reducing
    the risk of production OOM from hung offer futures.
    
    Tests:
    - sbt "stream / Test / compile"
    
    References:
    Refs #3095
---
 .../pekko/stream/InvokeWithFeedbackBenchmark.scala |  2 ++
 docs/src/main/paradox/stream/actor-interop.md      |  8 +++++++
 .../operators/Source/actorRefWithBackpressure.md   |  2 +-
 .../main/paradox/stream/operators/Source/queue.md  | 26 ++++++++++++++++++++++
 .../scala/docs/stream/IntegrationDocSpec.scala     | 10 ++++-----
 .../pekko/stream/scaladsl/QueueSourceSpec.scala    |  1 +
 .../org/apache/pekko/stream/javadsl/Source.scala   | 12 ++++++++++
 .../org/apache/pekko/stream/scaladsl/Source.scala  | 13 ++++++++++-
 8 files changed, 66 insertions(+), 8 deletions(-)

diff --git 
a/bench-jmh/src/main/scala/org/apache/pekko/stream/InvokeWithFeedbackBenchmark.scala
 
b/bench-jmh/src/main/scala/org/apache/pekko/stream/InvokeWithFeedbackBenchmark.scala
index 17c30e5f94..caba964889 100644
--- 
a/bench-jmh/src/main/scala/org/apache/pekko/stream/InvokeWithFeedbackBenchmark.scala
+++ 
b/bench-jmh/src/main/scala/org/apache/pekko/stream/InvokeWithFeedbackBenchmark.scala
@@ -15,6 +15,7 @@ package org.apache.pekko.stream
 
 import java.util.concurrent.TimeUnit
 
+import scala.annotation.nowarn
 import scala.concurrent._
 import scala.concurrent.duration._
 
@@ -27,6 +28,7 @@ import pekko.stream.scaladsl._
 @State(Scope.Benchmark)
 @OutputTimeUnit(TimeUnit.SECONDS)
 @BenchmarkMode(Array(Mode.Throughput))
+@nowarn("msg=deprecated")
 class InvokeWithFeedbackBenchmark {
   implicit val system: ActorSystem = ActorSystem("InvokeWithFeedbackBenchmark")
 
diff --git a/docs/src/main/paradox/stream/actor-interop.md 
b/docs/src/main/paradox/stream/actor-interop.md
index cd1bee5a16..0b04d99266 100644
--- a/docs/src/main/paradox/stream/actor-interop.md
+++ b/docs/src/main/paradox/stream/actor-interop.md
@@ -128,6 +128,14 @@ use `Sink.actorRefWithBackpressure` or `ask` in 
`mapAsync`, though.
 
 ### Source.queue
 
+@@@ warning { title="Deprecation notice (since 2.0.0)" }
+
+The `Source.queue(Int, OverflowStrategy)` overloads that this section 
describes — including `OverflowStrategy.backpressure` — are **deprecated** 
because their asynchronous `offer` @scala[`Future`]@java[`CompletionStage`] can 
hang indefinitely when downstream stalls. Use `Source.queue[T](bufferSize)` 
(materializes a @apidoc[BoundedSourceQueue] with synchronous feedback), or for 
backpressure towards the producer use 
@ref:[`Source.actorRefWithBackpressure`](operators/Source/actorRefWithBack [...]
+
+The snippet below has been updated to the recommended non-deprecated API, so 
it uses a synchronous `match`/`switch` on `QueueOfferResult` rather than the 
`pipe`-the-`Future` pattern described in the surrounding prose.
+
+@@@
+
 `Source.queue` is an improvement over `Sink.actorRef`, since it can provide 
backpressure.
 The `offer` method returns a @scala[`Future`]@java[`CompletionStage`], which 
completes with the result of the enqueue operation.
 
diff --git 
a/docs/src/main/paradox/stream/operators/Source/actorRefWithBackpressure.md 
b/docs/src/main/paradox/stream/operators/Source/actorRefWithBackpressure.md
index 9d3601abdf..3061057ead 100644
--- a/docs/src/main/paradox/stream/operators/Source/actorRefWithBackpressure.md
+++ b/docs/src/main/paradox/stream/operators/Source/actorRefWithBackpressure.md
@@ -18,7 +18,7 @@ See also:
 * @ref[Source.actorRef](../Source/actorRef.md) This operator without 
backpressure control
 * @ref[ActorSource.actorRef](../ActorSource/actorRef.md) The operator for the 
new actors API without backpressure control
 * 
@ref[ActorSource.actorRefWithBackpressure](../ActorSource/actorRefWithBackpressure.md)
 The corresponding operator for the new actors API
-* @ref[Source.queue](../Source/queue.md) Materialize a `SourceQueue` onto 
which elements can be pushed for emitting from the source
+* @ref[Source.queue](../Source/queue.md) Materialize a `BoundedSourceQueue` 
onto which elements can be pushed for emitting from the source
 
 ## Examples
 
diff --git a/docs/src/main/paradox/stream/operators/Source/queue.md 
b/docs/src/main/paradox/stream/operators/Source/queue.md
index 71875dadeb..4fc5235162 100644
--- a/docs/src/main/paradox/stream/operators/Source/queue.md
+++ b/docs/src/main/paradox/stream/operators/Source/queue.md
@@ -4,6 +4,14 @@ Materialize a `BoundedSourceQueue` or `SourceQueue` onto which 
elements can be p
 
 @ref[Source operators](../index.md#source-operators)
 
+@@@ warning { title="Deprecation notice (since 2.0.0)" }
+
+The `Source.queue` overloads that accept an @apidoc[OverflowStrategy] and 
materialize a `SourceQueueWithComplete` are **deprecated**. Their asynchronous 
`offer` @scala[`Future`]@java[`CompletionStage`] can hang indefinitely under 
`OverflowStrategy.backpressure` when downstream stalls, which has caused 
real-world deadlocks.
+
+Prefer `Source.queue[T](bufferSize)` (this page), which materializes a 
@apidoc[BoundedSourceQueue] with synchronous feedback and drop-newest overflow. 
For backpressure towards the producer, use 
@ref:[`Source.actorRefWithBackpressure`](actorRefWithBackpressure.md) (single 
imperative producer) or `MergeHub.source` (multiple producers). See the 
[migration 
table](#migrating-from-the-deprecated-sourcequeueint-overflowstrategy-overloads)
 below for a per-strategy replacement.
+
+@@@
+
 ## Signature (`BoundedSourceQueue`)
 
 @apidoc[Source.queue](Source$) { 
scala="#queue[T](bufferSize:Int):org.apache.pekko.stream.scaladsl.Source[T,org.apache.pekko.stream.scaladsl.BoundedSourceQueue[T]]"
 java="#queue(int)" }
@@ -35,6 +43,12 @@ Java
 
 ## Signature (`SourceQueue`)
 
+@@@ warning
+
+These two overloads are deprecated since 2.0.0. See the [migration 
table](#migrating-from-the-deprecated-sourcequeueint-overflowstrategy-overloads)
 below.
+
+@@@
+
 @apidoc[Source.queue](Source$) { 
scala="#queue[T](bufferSize:Int,overflowStrategy:org.apache.pekko.stream.OverflowStrategy):org.apache.pekko.stream.scaladsl.Source[T,org.apache.pekko.stream.scaladsl.SourceQueueWithComplete[T]]"
 java="#queue(int,org.apache.pekko.stream.OverflowStrategy)" }
 @apidoc[Source.queue](Source$) { 
scala="#queue[T](bufferSize:Int,overflowStrategy:org.apache.pekko.stream.OverflowStrategy,maxConcurrentOffers:Int):org.apache.pekko.stream.scaladsl.Source[T,org.apache.pekko.stream.scaladsl.SourceQueueWithComplete[T]]"
 java="#queue(int,org.apache.pekko.stream.OverflowStrategy,int)" }
 
@@ -69,3 +83,15 @@ Java
 
 @@@
 
+## Migrating from the deprecated `Source.queue(Int, OverflowStrategy)` 
overloads
+
+| Old call | Replacement |
+|----------|-------------|
+| `Source.queue(n, OverflowStrategy.dropNew)` | `Source.queue[T](n)` — 
`BoundedSourceQueue` already drops the newest element. |
+| `Source.queue(n, OverflowStrategy.dropHead)` | `Source.queue[T](n)` if 
drop-new is acceptable. Otherwise build a custom @apidoc[GraphStage] with a 
FIFO buffer that drops the head. |
+| `Source.queue(n, OverflowStrategy.dropTail)` | Same as above; 
`BoundedSourceQueue` always drops the newest offer (i.e. the tail). |
+| `Source.queue(n, OverflowStrategy.dropBuffer)` | `Source.queue[T](n)` 
combined with a @apidoc[GraphStage] that clears the buffer on overflow, or 
rework the producer to tolerate drops. |
+| `Source.queue(n, OverflowStrategy.fail)` | `Source.queue[T](n)` and, on 
`QueueOfferResult.Dropped`, call `BoundedSourceQueue.fail` with a 
`BufferOverflowException`. |
+| `Source.queue(n, OverflowStrategy.backpressure)` | 
@ref:[`Source.actorRefWithBackpressure`](actorRefWithBackpressure.md) (single 
imperative producer) or `MergeHub.source` (multiple producers). |
+
+`SourceQueueWithComplete.offer` returned a 
@scala[`Future[QueueOfferResult]`]@java[`CompletionStage<QueueOfferResult>`]; 
`BoundedSourceQueue.offer` returns `QueueOfferResult` synchronously. Call sites 
that previously chained `.map`/`.flatMap` on the offer future can usually be 
rewritten as a direct `match`/`switch` on the result.
diff --git a/docs/src/test/scala/docs/stream/IntegrationDocSpec.scala 
b/docs/src/test/scala/docs/stream/IntegrationDocSpec.scala
index 02df48ced3..2518cf09bd 100644
--- a/docs/src/test/scala/docs/stream/IntegrationDocSpec.scala
+++ b/docs/src/test/scala/docs/stream/IntegrationDocSpec.scala
@@ -478,7 +478,7 @@ class IntegrationDocSpec extends 
PekkoSpec(IntegrationDocSpec.config) {
     val bufferSize = 10
     val elementsToProcess = 5
 
-    val queue = Source
+    val queue: BoundedSourceQueue[Int] = Source
       .queue[Int](bufferSize)
       .throttle(elementsToProcess, 3.second)
       .map(x => x * x)
@@ -487,16 +487,15 @@ class IntegrationDocSpec extends 
PekkoSpec(IntegrationDocSpec.config) {
 
     val source = Source(1 to 10)
 
-    implicit val ec = system.dispatcher
     source
-      .map(x => {
-        queue.offer(x).map {
+      .map { x =>
+        queue.offer(x) match {
           case QueueOfferResult.Enqueued    => println(s"enqueued $x")
           case QueueOfferResult.Dropped     => println(s"dropped $x")
           case QueueOfferResult.Failure(ex) => println(s"Offer failed 
${ex.getMessage}")
           case QueueOfferResult.QueueClosed => println("Source Queue closed")
         }
-      })
+      }
       .runWith(Sink.ignore)
     // #source-queue
   }
@@ -519,7 +518,6 @@ class IntegrationDocSpec extends 
PekkoSpec(IntegrationDocSpec.config) {
 
     val fastElements = 1 to 10
 
-    implicit val ec = system.dispatcher
     fastElements.foreach { x =>
       queue.offer(x) match {
         case QueueOfferResult.Enqueued    => println(s"enqueued $x")
diff --git 
a/stream-tests/src/test/scala/org/apache/pekko/stream/scaladsl/QueueSourceSpec.scala
 
b/stream-tests/src/test/scala/org/apache/pekko/stream/scaladsl/QueueSourceSpec.scala
index 559369ce26..320a00d6b6 100644
--- 
a/stream-tests/src/test/scala/org/apache/pekko/stream/scaladsl/QueueSourceSpec.scala
+++ 
b/stream-tests/src/test/scala/org/apache/pekko/stream/scaladsl/QueueSourceSpec.scala
@@ -32,6 +32,7 @@ import pekko.testkit.TestProbe
 
 import org.scalatest.time.Span
 
+@nowarn("msg=deprecated")
 class QueueSourceSpec extends StreamSpec {
   implicit val ec: ExecutionContextExecutor = system.dispatcher
   val pause = 300.millis
diff --git a/stream/src/main/scala/org/apache/pekko/stream/javadsl/Source.scala 
b/stream/src/main/scala/org/apache/pekko/stream/javadsl/Source.scala
index 1b10205833..ed7af206ad 100755
--- a/stream/src/main/scala/org/apache/pekko/stream/javadsl/Source.scala
+++ b/stream/src/main/scala/org/apache/pekko/stream/javadsl/Source.scala
@@ -698,6 +698,12 @@ object Source {
    * @param bufferSize size of buffer in element count
    * @param overflowStrategy Strategy that is used when incoming elements 
cannot fit inside the buffer
    */
+  @deprecated(
+    "Prefer Source.queue(bufferSize) which materializes a BoundedSourceQueue 
with synchronous feedback " +
+    "(dropping the newest element when the buffer is full). For backpressure, 
use Source.actorRefWithBackpressure " +
+    "or MergeHub.source instead. See the Pekko Streams documentation for the 
Source.queue migration guide.",
+    since = "2.0.0")
+  @nowarn("msg=deprecated")
   def queue[T](bufferSize: Int, overflowStrategy: OverflowStrategy): Source[T, 
SourceQueueWithComplete[T]] =
     new Source(
       scaladsl.Source.queue[T](bufferSize, overflowStrategy, 
maxConcurrentOffers = 1).mapMaterializedValue(_.asJava))
@@ -736,6 +742,12 @@ object Source {
    * @param overflowStrategy Strategy that is used when incoming elements 
cannot fit inside the buffer
    * @param maxConcurrentOffers maximum number of pending offers when buffer 
is full, should be greater than 0.
    */
+  @deprecated(
+    "Prefer Source.queue(bufferSize) which materializes a BoundedSourceQueue 
with synchronous feedback " +
+    "(dropping the newest element when the buffer is full). For backpressure, 
use Source.actorRefWithBackpressure " +
+    "or MergeHub.source instead. See the Pekko Streams documentation for the 
Source.queue migration guide.",
+    since = "2.0.0")
+  @nowarn("msg=deprecated")
   def queue[T](
       bufferSize: Int,
       overflowStrategy: OverflowStrategy,
diff --git 
a/stream/src/main/scala/org/apache/pekko/stream/scaladsl/Source.scala 
b/stream/src/main/scala/org/apache/pekko/stream/scaladsl/Source.scala
index e4b5fc9bbe..a8038b708f 100644
--- a/stream/src/main/scala/org/apache/pekko/stream/scaladsl/Source.scala
+++ b/stream/src/main/scala/org/apache/pekko/stream/scaladsl/Source.scala
@@ -15,7 +15,7 @@ package org.apache.pekko.stream.scaladsl
 
 import java.util.concurrent.CompletionStage
 
-import scala.annotation.{ switch, tailrec, varargs }
+import scala.annotation.{ nowarn, switch, tailrec, varargs }
 import scala.annotation.unchecked.uncheckedVariance
 import scala.collection.{ immutable, AbstractIterator }
 import scala.concurrent.{ Future, Promise }
@@ -1011,6 +1011,12 @@ object Source {
    * @param bufferSize size of buffer in element count
    * @param overflowStrategy Strategy that is used when incoming elements 
cannot fit inside the buffer
    */
+  @deprecated(
+    "Prefer Source.queue[T](bufferSize) which materializes a 
BoundedSourceQueue with synchronous feedback " +
+    "(dropping the newest element when the buffer is full). For backpressure, 
use Source.actorRefWithBackpressure " +
+    "or MergeHub.source instead. See the Pekko Streams documentation for the 
Source.queue migration guide.",
+    since = "2.0.0")
+  @nowarn("msg=deprecated")
   def queue[T](bufferSize: Int, overflowStrategy: OverflowStrategy): Source[T, 
SourceQueueWithComplete[T]] =
     queue(bufferSize, overflowStrategy, maxConcurrentOffers = 1)
 
@@ -1048,6 +1054,11 @@ object Source {
    * @param overflowStrategy Strategy that is used when incoming elements 
cannot fit inside the buffer
    * @param maxConcurrentOffers maximum number of pending offers when buffer 
is full, should be greater than 0.
    */
+  @deprecated(
+    "Prefer Source.queue[T](bufferSize) which materializes a 
BoundedSourceQueue with synchronous feedback " +
+    "(dropping the newest element when the buffer is full). For backpressure, 
use Source.actorRefWithBackpressure " +
+    "or MergeHub.source instead. See the Pekko Streams documentation for the 
Source.queue migration guide.",
+    since = "2.0.0")
   def queue[T](
       bufferSize: Int,
       overflowStrategy: OverflowStrategy,


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to