|
18 | 18 | import java.util.Collection; |
19 | 19 | import java.util.Collections; |
20 | 20 | import java.util.HashMap; |
| 21 | +import java.util.HashSet; |
21 | 22 | import java.util.Map; |
| 23 | +import java.util.Set; |
22 | 24 | import java.util.concurrent.ConcurrentHashMap; |
23 | 25 |
|
24 | 26 | import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisherFactory; |
|
29 | 31 | import rx.Observable; |
30 | 32 | import rx.Scheduler; |
31 | 33 | import rx.functions.Action0; |
| 34 | +import rx.functions.Action1; |
32 | 35 | import rx.functions.Func1; |
33 | 36 | import rx.schedulers.Schedulers; |
34 | 37 | import rx.subjects.ReplaySubject; |
@@ -171,32 +174,46 @@ public Observable<Void> mapResponseToRequests(Observable<BatchReturnType> batchR |
171 | 174 | // index the requests by key |
172 | 175 | final Map<K, CollapsedRequest<ResponseType, RequestArgumentType>> requestsByKey = new HashMap<K, CollapsedRequest<ResponseType, RequestArgumentType>>(requests.size()); |
173 | 176 | for (CollapsedRequest<ResponseType, RequestArgumentType> cr : requests) { |
174 | | - requestsByKey.put(requestKeySelector.call(cr.getArgument()), cr); |
| 177 | + K requestArg = requestKeySelector.call(cr.getArgument()); |
| 178 | + requestsByKey.put(requestArg, cr); |
175 | 179 | } |
| 180 | + final Set<K> seenKeys = new HashSet<K>(); |
176 | 181 |
|
177 | 182 | // observe the responses and join with the requests by key |
178 | | - return batchResponse.flatMap(new Func1<BatchReturnType, Observable<Void>>() { |
179 | | - |
| 183 | + return batchResponse.doOnNext(new Action1<BatchReturnType>() { |
180 | 184 | @Override |
181 | | - public Observable<Void> call(BatchReturnType r) { |
182 | | - K responseKey = batchResponseKeySelector.call(r); |
183 | | - CollapsedRequest<ResponseType, RequestArgumentType> requestForResponse = requestsByKey.get(responseKey); |
184 | | - requestForResponse.setResponse(mapBatchTypeToResponseType.call(r)); |
185 | | - // now remove from map so we know what wasn't set at end |
186 | | - requestsByKey.remove(responseKey); |
187 | | - return Observable.empty(); |
| 185 | + public void call(BatchReturnType batchReturnType) { |
| 186 | + try { |
| 187 | + K responseKey = batchResponseKeySelector.call(batchReturnType); |
| 188 | + CollapsedRequest<ResponseType, RequestArgumentType> requestForResponse = requestsByKey.get(responseKey); |
| 189 | + if (requestForResponse != null) { |
| 190 | + requestForResponse.emitResponse(mapBatchTypeToResponseType.call(batchReturnType)); |
| 191 | + // now add this to seenKeys, so we can later check what was seen, and what was unseen |
| 192 | + seenKeys.add(responseKey); |
| 193 | + } else { |
| 194 | + logger.warn("Batch Response contained a response key not in request batch : " + responseKey); |
| 195 | + } |
| 196 | + } catch (Throwable ex) { |
| 197 | + logger.warn("Uncaught error during demultiplexing of BatchResponse", ex); |
| 198 | + } |
188 | 199 | } |
189 | | - |
190 | 200 | }).doOnTerminate(new Action0() { |
191 | | - |
192 | 201 | @Override |
193 | 202 | public void call() { |
194 | | - for (CollapsedRequest<ResponseType, RequestArgumentType> cr : requestsByKey.values()) { |
195 | | - onMissingResponse(cr); |
| 203 | + for (K key: requestsByKey.keySet()) { |
| 204 | + CollapsedRequest<ResponseType, RequestArgumentType> collapsedReq = requestsByKey.get(key); |
| 205 | + if (!seenKeys.contains(key)) { |
| 206 | + try { |
| 207 | + onMissingResponse(collapsedReq); |
| 208 | + } catch (Throwable ex) { |
| 209 | + collapsedReq.setException(new RuntimeException("Error in HystrixObservableCollapser.onMissingResponse handler", ex)); |
| 210 | + } |
| 211 | + } |
| 212 | + //then unconditionally issue an onCompleted. this ensures the downstream gets a terminal, regardless of how onMissingResponse was implemented |
| 213 | + collapsedReq.setComplete(); |
196 | 214 | } |
197 | 215 | } |
198 | | - |
199 | | - }); |
| 216 | + }).ignoreElements().cast(Void.class); |
200 | 217 | } |
201 | 218 |
|
202 | 219 | @Override |
|
0 commit comments