1717
1818import java .nio .ByteBuffer ;
1919import java .util .concurrent .CompletableFuture ;
20+ import org .reactivestreams .Subscriber ;
21+ import org .reactivestreams .Subscription ;
2022import software .amazon .awssdk .annotations .SdkInternalApi ;
2123import software .amazon .awssdk .core .ResponseInputStream ;
2224import software .amazon .awssdk .core .SdkResponse ;
@@ -35,6 +37,7 @@ public class InputStreamResponseTransformer<ResponseT extends SdkResponse>
3537
3638 private volatile CompletableFuture <ResponseInputStream <ResponseT >> future ;
3739 private volatile ResponseT response ;
40+ private volatile WaitForSubscribeOnErrorWrapper subscriber ;
3841
3942 @ Override
4043 public CompletableFuture <ResponseInputStream <ResponseT >> prepare () {
@@ -51,17 +54,74 @@ public void onResponse(ResponseT response) {
5154 @ Override
5255 public void onStream (SdkPublisher <ByteBuffer > publisher ) {
5356 AbortableInputStreamSubscriber inputStreamSubscriber = AbortableInputStreamSubscriber .builder ().build ();
54- publisher .subscribe (inputStreamSubscriber );
57+ WaitForSubscribeOnErrorWrapper waitForSubscribeSubscriber = new WaitForSubscribeOnErrorWrapper (inputStreamSubscriber );
58+
59+ this .subscriber = waitForSubscribeSubscriber ;
60+
61+ publisher .subscribe (waitForSubscribeSubscriber );
5562 future .complete (new ResponseInputStream <>(response , inputStreamSubscriber ));
5663 }
5764
5865 @ Override
5966 public void exceptionOccurred (Throwable error ) {
6067 future .completeExceptionally (error );
68+ if (subscriber != null ) {
69+ this .subscriber .onError (error );
70+ }
6171 }
6272
6373 @ Override
6474 public String name () {
6575 return TransformerType .STREAM .getName ();
6676 }
77+
78+ // Simple wrapper subscriber that ensures we don't forward the `onError` to the delegate until onSubscribe is called, to be
79+ // compliant with the reactive streams spec. We use onError for forwarding the exception given to exceptionOccurred.
80+ private static final class WaitForSubscribeOnErrorWrapper implements Subscriber <ByteBuffer > {
81+ private final Object lock = new Object ();
82+ private final AbortableInputStreamSubscriber delegate ;
83+
84+ private boolean subscribed = false ;
85+ private Throwable transformerException ;
86+
87+
88+ private WaitForSubscribeOnErrorWrapper (AbortableInputStreamSubscriber delegate ) {
89+ this .delegate = delegate ;
90+ }
91+
92+ @ Override
93+ public void onSubscribe (Subscription s ) {
94+ synchronized (lock ) {
95+ subscribed = true ;
96+ delegate .onSubscribe (s );
97+
98+ if (transformerException != null ) {
99+ delegate .onError (transformerException );
100+ transformerException = null ;
101+ }
102+ }
103+ }
104+
105+ @ Override
106+ public void onNext (ByteBuffer byteBuffer ) {
107+ this .delegate .onNext (byteBuffer );
108+ }
109+
110+ @ Override
111+ public void onError (Throwable t ) {
112+ synchronized (lock ) {
113+ if (subscribed ) {
114+ delegate .onError (t );
115+ } else {
116+ // We're not subscribed yet, save the exception for until we are.
117+ transformerException = t ;
118+ }
119+ }
120+ }
121+
122+ @ Override
123+ public void onComplete () {
124+ this .delegate .onComplete ();
125+ }
126+ }
67127}
0 commit comments