use of io.reactivx.mantis.operators.DisableBackPressureOperator in project mantis by Netflix.
the class ObservableTrigger method groupTrigger.
private static <K, V> PushTrigger<KeyValuePair<K, V>> groupTrigger(final String name, final Observable<GroupedObservable<K, V>> o, final Action0 doOnComplete, final Action1<Throwable> doOnError, final long groupExpirySeconds, final Func1<K, byte[]> keyEncoder, final HashFunction hashFunction) {
final AtomicReference<Subscription> subRef = new AtomicReference<>();
final Gauge subscriptionActive;
Metrics metrics = new Metrics.Builder().name("ObservableTrigger_" + name).addGauge("subscriptionActive").build();
subscriptionActive = metrics.getGauge("subscriptionActive");
Action1<MonitoredQueue<KeyValuePair<K, V>>> doOnStart = new Action1<MonitoredQueue<KeyValuePair<K, V>>>() {
@Override
public void call(final MonitoredQueue<KeyValuePair<K, V>> queue) {
subRef.set(o.observeOn(Schedulers.computation()).doOnSubscribe(() -> {
logger.info("Subscription is ACTIVE for observable trigger with name: " + name);
subscriptionActive.set(1);
}).doOnUnsubscribe(() -> {
logger.info("Subscription is INACTIVE for observable trigger with name: " + name);
subscriptionActive.set(0);
}).flatMap((final GroupedObservable<K, V> group) -> {
final byte[] keyBytes = keyEncoder.call(group.getKey());
final long keyBytesHashed = hashFunction.computeHash(keyBytes);
return group.timeout(groupExpirySeconds, TimeUnit.SECONDS, (Observable<? extends V>) Observable.empty()).lift(new DisableBackPressureOperator<V>()).buffer(250, TimeUnit.MILLISECONDS).filter((List<V> t1) -> t1 != null && !t1.isEmpty()).map((List<V> list) -> {
List<KeyValuePair<K, V>> keyPairList = new ArrayList<>(list.size());
for (V data : list) {
keyPairList.add(new KeyValuePair<K, V>(keyBytesHashed, keyBytes, data));
}
return keyPairList;
});
}).subscribe((List<KeyValuePair<K, V>> list) -> {
for (KeyValuePair<K, V> data : list) {
queue.write(data);
}
}, (Throwable e) -> {
logger.warn("Observable used to push data errored, on server with name: " + name, e);
if (doOnError != null) {
doOnError.call(e);
}
}, () -> {
logger.info("Observable used to push data completed, on server with name: " + name);
if (doOnComplete != null) {
doOnComplete.call();
}
}));
}
};
Action1<MonitoredQueue<KeyValuePair<K, V>>> doOnStop = new Action1<MonitoredQueue<KeyValuePair<K, V>>>() {
@Override
public void call(MonitoredQueue<KeyValuePair<K, V>> t1) {
if (subRef.get() != null) {
logger.warn("Connections from next stage has dropped to 0. Do not propagate unsubscribe");
// subRef.get().unsubscribe();
}
}
};
return new PushTrigger<>(doOnStart, doOnStop, metrics);
}
use of io.reactivx.mantis.operators.DisableBackPressureOperator in project mantis by Netflix.
the class ServeGroupedObservable method applySlottingSideEffectToObservable.
private void applySlottingSideEffectToObservable(Observable<Observable<GroupedObservable<String, V>>> o, final Observable<Integer> minConnectionsToSubscribe) {
final AtomicInteger currentMinConnectionsToSubscribe = new AtomicInteger();
minConnectionsToSubscribe.subscribe(new Action1<Integer>() {
@Override
public void call(Integer t1) {
currentMinConnectionsToSubscribe.set(t1);
}
});
Observable<Observable<List<Group<String, V>>>> listOfGroups = o.map(new Func1<Observable<GroupedObservable<String, V>>, Observable<List<Group<String, V>>>>() {
@Override
public Observable<List<Group<String, V>>> call(Observable<GroupedObservable<String, V>> og) {
return og.flatMap(new Func1<GroupedObservable<String, V>, Observable<List<Group<String, V>>>>() {
@Override
public Observable<List<Group<String, V>>> call(final GroupedObservable<String, V> group) {
final byte[] keyBytes = keyEncoder.encode(group.getKey());
final String keyValue = group.getKey();
return group.doOnUnsubscribe(new Action0() {
@Override
public void call() {
// logger.info("Expiring group stage in serveGroupedObservable " + group.getKey());
groupsExpiredCounter.increment();
}
}).timeout(expiryInSecs, TimeUnit.SECONDS, (Observable<? extends V>) Observable.empty()).materialize().lift(new DisableBackPressureOperator<Notification<V>>()).buffer(groupBufferTimeMSec, TimeUnit.MILLISECONDS).filter(new Func1<List<Notification<V>>, Boolean>() {
@Override
public Boolean call(List<Notification<V>> t1) {
return t1 != null && !t1.isEmpty();
}
}).map(new Func1<List<Notification<V>>, List<Group<String, V>>>() {
@Override
public List<Group<String, V>> call(List<Notification<V>> notifications) {
List<Group<String, V>> groups = new ArrayList<>(notifications.size());
for (Notification<V> notification : notifications) {
groups.add(new Group<String, V>(keyValue, keyBytes, notification));
}
return groups;
}
});
}
});
}
});
final Observable<List<Group<String, V>>> withSideEffects = Observable.merge(listOfGroups).doOnEach(new Observer<List<Group<String, V>>>() {
@Override
public void onCompleted() {
slottingStrategy.completeAllConnections();
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
slottingStrategy.errorAllConnections(e);
}
@Override
public void onNext(List<Group<String, V>> listOfGroups) {
for (Group<String, V> group : listOfGroups) {
slottingStrategy.writeOnSlot(group.getKeyBytes(), group);
}
}
});
final MutableReference<Subscription> subscriptionRef = new MutableReference<>();
final AtomicInteger connectionCount = new AtomicInteger(0);
final AtomicBoolean isSubscribed = new AtomicBoolean();
slottingStrategy.registerDoOnEachConnectionAdded(new Action0() {
@Override
public void call() {
Integer minNeeded = currentMinConnectionsToSubscribe.get();
Integer current = connectionCount.incrementAndGet();
if (current >= minNeeded) {
if (isSubscribed.compareAndSet(false, true)) {
logger.info("MinConnectionsToSubscribe: " + minNeeded + ", has been met, subscribing to observable, current connection count: " + current);
subscriptionRef.setValue(withSideEffects.subscribe());
}
} else {
logger.info("MinConnectionsToSubscribe: " + minNeeded + ", has NOT been met, current connection count: " + current);
}
}
});
slottingStrategy.registerDoAfterLastConnectionRemoved(new Action0() {
@Override
public void call() {
subscriptionRef.getValue().unsubscribe();
logger.info("All connections deregistered, unsubscribed to observable, resetting current connection count: 0");
connectionCount.set(0);
isSubscribed.set(false);
}
});
}
use of io.reactivx.mantis.operators.DisableBackPressureOperator in project mantis by Netflix.
the class ServerSentEventRequestHandler method handle.
@Override
public Observable<Void> handle(HttpServerRequest<ByteBuf> request, final HttpServerResponse<ServerSentEvent> response) {
InetSocketAddress socketAddress = (InetSocketAddress) response.getChannel().remoteAddress();
LOG.info("HTTP SSE connection received from " + socketAddress.getAddress() + ":" + socketAddress.getPort() + " queryParams: " + request.getQueryParameters());
final String socketAddrStr = socketAddress.getAddress().toString();
final WritableEndpoint<String> sn = new WritableEndpoint<>(socketAddress.getHostString(), socketAddress.getPort(), Endpoint.uniqueHost(socketAddress.getHostString(), socketAddress.getPort(), null));
final Map<String, List<String>> queryParameters = request.getQueryParameters();
final SlotAssignmentManager<String> slotMgr = ssm.registerServer(sn, queryParameters);
final AtomicLong lastResponseFlush = new AtomicLong();
lastResponseFlush.set(-1);
final AtomicLong lastResponseSent = new AtomicLong(-1);
// copy reference, then apply request specific filters, sampling
Observable<T> requestObservable = observableToServe;
// decouple the observable on a separate thread and add backpressure handling
// ServiceRegistry.INSTANCE.getPropertiesService().getStringValue("sse.decouple", "false");
String decoupleSSE = "false";
if ("true".equals(decoupleSSE)) {
final BasicTag sockAddrTag = new BasicTag("sockAddr", Optional.ofNullable(socketAddrStr).orElse("none"));
requestObservable = requestObservable.lift(new DropOperator<T>("outgoing_ServerSentEventRequestHandler", sockAddrTag)).observeOn(Schedulers.io());
}
response.getHeaders().set("Access-Control-Allow-Origin", "*");
response.getHeaders().set("content-type", "text/event-stream");
response.getHeaders().set("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate");
response.getHeaders().set("Pragma", "no-cache");
response.flush();
String uniqueClientId = socketAddrStr;
if (queryParameters != null && queryParameters.containsKey(CLIENT_ID_PARAM)) {
// enablePings
uniqueClientId = queryParameters.get(CLIENT_ID_PARAM).get(0);
}
if (queryParameters != null && queryParameters.containsKey(FORMAT_PARAM)) {
format = queryParameters.get(FORMAT_PARAM).get(0);
}
if (queryParameters != null && requestPreprocessor != null) {
requestPreprocessor.call(queryParameters, context);
}
// apply sampling, milli, then seconds
if (queryParameters != null && queryParameters.containsKey(SAMPLE_PARAM_MSEC)) {
// apply sampling rate
int samplingRate = Integer.parseInt(queryParameters.get(SAMPLE_PARAM_MSEC).get(0));
requestObservable = requestObservable.sample(samplingRate, TimeUnit.MILLISECONDS);
}
if (queryParameters != null && queryParameters.containsKey(SAMPLE_PARAM)) {
// apply sampling rate
int samplingRate = Integer.parseInt(queryParameters.get(SAMPLE_PARAM).get(0));
requestObservable = requestObservable.sample(samplingRate, TimeUnit.SECONDS);
}
if (queryParameters != null && queryParameters.containsKey(ENABLE_PINGS_PARAM)) {
// enablePings
String enablePings = queryParameters.get(ENABLE_PINGS_PARAM).get(0);
if ("true".equalsIgnoreCase(enablePings)) {
pingsEnabled = true;
} else {
pingsEnabled = false;
}
}
if (queryParameters != null && queryParameters.containsKey("delay")) {
// apply flush
try {
int flushInterval = Integer.parseInt(queryParameters.get("delay").get(0));
if (flushInterval >= 50) {
flushIntervalMillis = flushInterval;
} else {
LOG.warn("delay parameter too small " + flushInterval + " min. is 100");
}
} catch (Exception e) {
e.printStackTrace();
}
}
final byte[] delimiter = queryParameters != null && queryParameters.containsKey(MantisSSEConstants.MANTIS_COMPRESSION_DELIMITER) && queryParameters.get(MantisSSEConstants.MANTIS_COMPRESSION_DELIMITER).get(0) != null ? queryParameters.get(MantisSSEConstants.MANTIS_COMPRESSION_DELIMITER).get(0).getBytes() : null;
Tag[] tags = new Tag[2];
final String clientId = Optional.ofNullable(uniqueClientId).orElse("none");
final String sockAddr = Optional.ofNullable(socketAddrStr).orElse("none");
tags[0] = new BasicTag("clientId", clientId);
tags[1] = new BasicTag("sockAddr", sockAddr);
Metrics sseSinkMetrics = new Metrics.Builder().id("ServerSentEventRequestHandler", tags).addCounter("processedCounter").addCounter("pingCounter").addCounter("errorCounter").addCounter("droppedCounter").addCounter("flushCounter").build();
final Counter msgProcessedCounter = sseSinkMetrics.getCounter("processedCounter");
final Counter pingCounter = sseSinkMetrics.getCounter("pingCounter");
final Counter errorCounter = sseSinkMetrics.getCounter("errorCounter");
final Counter droppedWrites = sseSinkMetrics.getCounter("droppedCounter");
final Counter flushCounter = sseSinkMetrics.getCounter("flushCounter");
// get predicate, defaults to return true for all T
Func1<T, Boolean> filterFunction = new Func1<T, Boolean>() {
@Override
public Boolean call(T t1) {
return true;
}
};
if (queryParameters != null && predicate != null) {
filterFunction = predicate.getPredicate().call(queryParameters);
}
final Subscription timerSubscription = Observable.interval(1, TimeUnit.SECONDS).doOnNext(new Action1<Long>() {
@Override
public void call(Long t1) {
long currentTime = System.currentTimeMillis();
if (pingsEnabled && (lastResponseSent.get() == -1 || currentTime > lastResponseSent.get() + PING_INTERVAL)) {
pingCounter.increment();
response.writeStringAndFlush(PING);
lastResponseSent.set(currentTime);
}
}
}).subscribe();
return requestObservable.filter(filterFunction).map(encoder).lift(new DisableBackPressureOperator<String>()).buffer(flushIntervalMillis, TimeUnit.MILLISECONDS).flatMap(new Func1<List<String>, Observable<Void>>() {
@Override
public Observable<Void> call(List<String> valueList) {
if (response.isCloseIssued() || !response.getChannel().isActive()) {
LOG.info("Client closed detected, throwing closed channel exception");
return Observable.error(new ClosedChannelException());
}
List<String> filteredList = valueList.stream().filter(e -> {
return slotMgr.filter(sn, e.getBytes());
}).collect(Collectors.toList());
if (response.getChannel().isWritable()) {
flushCounter.increment();
if (format.equals(BINARY_FORMAT)) {
boolean useSnappy = true;
try {
String compressedList = delimiter == null ? CompressionUtils.compressAndBase64Encode(filteredList, useSnappy) : CompressionUtils.compressAndBase64Encode(filteredList, useSnappy, delimiter);
StringBuilder sb = new StringBuilder(3);
sb.append(SSE_DATA_PREFIX);
sb.append(compressedList);
sb.append(TWO_NEWLINES);
msgProcessedCounter.increment(valueList.size());
lastResponseSent.set(System.currentTimeMillis());
return response.writeStringAndFlush(sb.toString());
} catch (Exception e) {
LOG.warn("Could not compress data" + e.getMessage());
droppedWrites.increment(valueList.size());
return Observable.empty();
}
} else {
int noOfMsgs = 0;
StringBuilder sb = new StringBuilder(valueList.size() * 3);
for (String s : filteredList) {
sb.append(SSE_DATA_PREFIX);
sb.append(s);
sb.append(TWO_NEWLINES);
noOfMsgs++;
}
msgProcessedCounter.increment(noOfMsgs);
lastResponseSent.set(System.currentTimeMillis());
return response.writeStringAndFlush(sb.toString());
}
} else {
//
droppedWrites.increment(filteredList.size());
}
return Observable.empty();
}
}).onErrorResumeNext(new Func1<Throwable, Observable<? extends Void>>() {
@Override
public Observable<? extends Void> call(Throwable throwable) {
Throwable cause = throwable.getCause();
// ignore closed channel exceptions, this is
// when the connection was closed on the client
// side without informing the server
errorCounter.increment();
if (cause != null && !(cause instanceof ClosedChannelException)) {
LOG.warn("Error detected in SSE sink", cause);
if (errorEncoder != null) {
// write error out on connection
// response.writeAndFlush(errorEncoder.call(throwable));
ByteBuf errType = response.getAllocator().buffer().writeBytes("error: ".getBytes());
ByteBuf errRes = response.getAllocator().buffer().writeBytes((errorEncoder.call(throwable)).getBytes());
response.writeAndFlush(ServerSentEvent.withEventType(errType, errRes));
}
throwable.printStackTrace();
}
if (requestPostprocessor != null && queryParameters != null) {
requestPostprocessor.call(queryParameters, context);
}
ssm.deregisterServer(sn, queryParameters);
timerSubscription.unsubscribe();
return Observable.error(throwable);
}
});
}
use of io.reactivx.mantis.operators.DisableBackPressureOperator in project mantis by Netflix.
the class RemoteObservableConnectionHandler method serveNestedObservable.
@SuppressWarnings({ "unchecked", "rawtypes" })
private <T> Subscription serveNestedObservable(final Observable<T> observable, final ObservableConnection<RemoteRxEvent, List<RemoteRxEvent>> connection, final RemoteRxEvent event, final Func1<Map<String, String>, Func1<T, Boolean>> filterFunction, final Encoder<T> encoder, final ServeNestedObservable<Observable<T>> serveConfig, final WritableEndpoint<Observable<T>> endpoint) {
final MutableReference<Subscription> subReference = new MutableReference<>();
subReference.setValue(observable.filter(filterFunction.call(event.getSubscribeParameters())).doOnCompleted(new Action0() {
@Override
public void call() {
logger.info("OnCompleted recieved in serveNestedObservable, sending to client.");
}
}).doOnError(new Action1<Throwable>() {
@Override
public void call(Throwable t1) {
logger.info("OnError received in serveNestedObservable, sending to client: ", t1);
}
}).map(new Func1<T, byte[]>() {
@Override
public byte[] call(T t1) {
return encoder.encode(t1);
}
}).materialize().map(new Func1<Notification<byte[]>, RemoteRxEvent>() {
@Override
public RemoteRxEvent call(Notification<byte[]> notification) {
if (notification.getKind() == Notification.Kind.OnNext) {
return RemoteRxEvent.next(event.getName(), notification.getValue());
} else if (notification.getKind() == Notification.Kind.OnError) {
return RemoteRxEvent.error(event.getName(), RemoteObservable.fromThrowableToBytes(notification.getThrowable()));
} else if (notification.getKind() == Notification.Kind.OnCompleted) {
return RemoteRxEvent.completed(event.getName());
} else {
throw new RuntimeException("Unsupported notification kind: " + notification.getKind());
}
}
}).lift(new DisableBackPressureOperator<RemoteRxEvent>()).buffer(writeBufferTimeMSec, TimeUnit.MILLISECONDS).filter(new Func1<List<RemoteRxEvent>, Boolean>() {
@Override
public Boolean call(List<RemoteRxEvent> t1) {
return t1 != null && !t1.isEmpty();
}
}).filter(new Func1<List<RemoteRxEvent>, Boolean>() {
@Override
public Boolean call(List<RemoteRxEvent> t1) {
return t1 != null && !t1.isEmpty();
}
}).subscribe(new WriteBytesObserver(connection, subReference, serverMetrics, serveConfig.getSlottingStrategy(), endpoint)));
return subReference.getValue();
}
Aggregations