1616package feign .graphql ;
1717
1818import feign .Capability ;
19+ import feign .Client ;
1920import feign .Contract ;
2021import feign .Experimental ;
2122import feign .RequestInterceptors ;
2425import feign .codec .JsonCodec ;
2526import feign .codec .JsonDecoder ;
2627import feign .codec .JsonEncoder ;
28+ import java .time .Duration ;
2729import java .util .ArrayList ;
30+ import java .util .concurrent .Executor ;
31+ import java .util .concurrent .Executors ;
32+ import java .util .concurrent .atomic .AtomicLong ;
2833
2934@ Experimental
3035public class GraphqlCapability implements Capability {
@@ -33,15 +38,70 @@ public class GraphqlCapability implements Capability {
3338 private final GraphqlEncoder graphqlEncoder ;
3439 private final GraphqlDecoder graphqlDecoder ;
3540 private final GraphqlRequestInterceptor interceptor ;
41+ private final JsonEncoder jsonEncoder ;
42+ private final JsonDecoder jsonDecoder ;
3643
3744 public GraphqlCapability (JsonCodec codec ) {
3845 this (codec .encoder (), codec .decoder ());
3946 }
4047
48+ /**
49+ * @param eventTimeout how long a blocking subscription call waits for an event before failing
50+ * with {@link java.net.SocketTimeoutException}; {@link java.time.Duration#ZERO} waits
51+ * indefinitely. Does not apply to {@code Flow.Publisher} or {@code CompletableFuture}
52+ * subscriptions, whose caller owns the deadline.
53+ */
54+ public GraphqlCapability (JsonCodec codec , Duration eventTimeout ) {
55+ this (codec .encoder (), codec .decoder (), eventTimeout );
56+ }
57+
58+ /**
59+ * @param executor runs the worker behind each {@code Flow.Publisher} and {@code
60+ * CompletableFuture} subscription, and delivers to their subscribers. Supply your own to own
61+ * the lifecycle; the default is bounded and daemon, and is never shut down.
62+ */
63+ public GraphqlCapability (JsonCodec codec , Duration eventTimeout , Executor executor ) {
64+ this (codec .encoder (), codec .decoder (), eventTimeout , executor );
65+ }
66+
4167 public GraphqlCapability (JsonEncoder encoder , JsonDecoder decoder ) {
68+ this (encoder , decoder , GraphqlDecoder .DEFAULT_EVENT_TIMEOUT );
69+ }
70+
71+ /**
72+ * @param eventTimeout see {@link #GraphqlCapability(JsonCodec, Duration)}
73+ */
74+ public GraphqlCapability (JsonEncoder encoder , JsonDecoder decoder , Duration eventTimeout ) {
75+ this (encoder , decoder , eventTimeout , defaultExecutor ());
76+ }
77+
78+ /**
79+ * @param executor see {@link #GraphqlCapability(JsonCodec, Duration, Executor)}
80+ */
81+ public GraphqlCapability (
82+ JsonEncoder encoder , JsonDecoder decoder , Duration eventTimeout , Executor executor ) {
4283 this .graphqlEncoder = new GraphqlEncoder (encoder , contract );
43- this .graphqlDecoder = new GraphqlDecoder (decoder );
84+ this .graphqlDecoder = new GraphqlDecoder (decoder , eventTimeout , executor );
4485 this .interceptor = new GraphqlRequestInterceptor (encoder , contract );
86+ this .jsonEncoder = encoder ;
87+ this .jsonDecoder = decoder ;
88+ }
89+
90+ /**
91+ * Each open {@code Flow.Publisher} or {@code CompletableFuture} subscription holds one worker for
92+ * its lifetime, so the default pool grows on demand and reaps idle threads rather than capping
93+ * concurrent subscriptions at a guess. Supply a bounded executor to cap them deliberately: the
94+ * excess is refused with {@code RejectedExecutionException} rather than left hanging.
95+ */
96+ private static Executor defaultExecutor () {
97+ var threads = new AtomicLong ();
98+ return Executors .newCachedThreadPool (
99+ runnable -> {
100+ var thread =
101+ new Thread (runnable , "feign-graphql-subscription-" + threads .incrementAndGet ());
102+ thread .setDaemon (true );
103+ return thread ;
104+ });
45105 }
46106
47107 @ Override
@@ -59,6 +119,11 @@ public Decoder enrich(Decoder decoder) {
59119 return graphqlDecoder ;
60120 }
61121
122+ @ Override
123+ public Client enrich (Client client ) {
124+ return new GraphqlSubscriptionClient (client , contract , jsonEncoder , jsonDecoder );
125+ }
126+
62127 @ Override
63128 public RequestInterceptors enrich (RequestInterceptors requestInterceptors ) {
64129 var enriched = new ArrayList <>(requestInterceptors .interceptors ());
0 commit comments