All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.rsocket.resume.ClientRSocketSession Maven / Gradle / Ivy

There is a newer version: 1.1.4
Show newest version
/*
 * Copyright 2015-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.rsocket.resume;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.rsocket.DuplexConnection;
import io.rsocket.exceptions.ConnectionErrorException;
import io.rsocket.frame.ErrorFrameFlyweight;
import io.rsocket.frame.ResumeFrameFlyweight;
import io.rsocket.frame.ResumeOkFrameFlyweight;
import io.rsocket.internal.ClientServerInputMultiplexer;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.retry.Retry;

public class ClientRSocketSession implements RSocketSession> {
  private static final Logger logger = LoggerFactory.getLogger(ClientRSocketSession.class);

  private final ResumableDuplexConnection resumableConnection;
  private volatile Mono newConnection;
  private volatile ByteBuf resumeToken;
  private final ByteBufAllocator allocator;

  public ClientRSocketSession(
      DuplexConnection duplexConnection,
      Duration resumeSessionDuration,
      Retry retry,
      ResumableFramesStore resumableFramesStore,
      Duration resumeStreamTimeout,
      boolean cleanupStoreOnKeepAlive) {
    this.allocator = duplexConnection.alloc();
    this.resumableConnection =
        new ResumableDuplexConnection(
            "client",
            duplexConnection,
            resumableFramesStore,
            resumeStreamTimeout,
            cleanupStoreOnKeepAlive);

    /*session completed: release token initially retained in resumeToken(ByteBuf)*/
    onClose().doFinally(s -> resumeToken.release()).subscribe();

    resumableConnection
        .connectionErrors()
        .flatMap(
            err -> {
              logger.debug("Client session connection error. Starting new connection");
              AtomicBoolean once = new AtomicBoolean();
              return newConnection
                  .delaySubscription(
                      once.compareAndSet(false, true)
                          ? retry.generateCompanion(Flux.just(new RetrySignal(err)))
                          : Mono.empty())
                  .retryWhen(retry)
                  .timeout(resumeSessionDuration);
            })
        .map(ClientServerInputMultiplexer::new)
        .subscribe(
            multiplexer -> {
              /*reconnect resumable connection*/
              reconnect(multiplexer.asClientServerConnection());
              long impliedPosition = resumableConnection.impliedPosition();
              long position = resumableConnection.position();
              logger.debug(
                  "Client ResumableConnection reconnected. Sending RESUME frame with state: [impliedPos: {}, pos: {}]",
                  impliedPosition,
                  position);
              /*Connection is established again: send RESUME frame to server, listen for RESUME_OK*/
              sendFrame(
                      ResumeFrameFlyweight.encode(
                          allocator,
                          /*retain so token is not released once sent as part of resume frame*/
                          resumeToken.retain(),
                          impliedPosition,
                          position))
                  .then(multiplexer.asSetupConnection().receive().next())
                  .subscribe(this::resumeWith);
            },
            err -> {
              logger.debug("Client ResumableConnection reconnect timeout");
              resumableConnection.dispose();
            });
  }

  @Override
  public ClientRSocketSession continueWith(Mono connectionFactory) {
    this.newConnection = connectionFactory;
    return this;
  }

  @Override
  public ClientRSocketSession resumeWith(ByteBuf resumeOkFrame) {
    logger.debug("ResumeOK FRAME received");
    long remotePos = remotePos(resumeOkFrame);
    long remoteImpliedPos = remoteImpliedPos(resumeOkFrame);
    resumeOkFrame.release();

    resumableConnection.resume(
        remotePos,
        remoteImpliedPos,
        pos ->
            pos.then()
                /*Resumption is impossible: send CONNECTION_ERROR*/
                .onErrorResume(
                    err ->
                        sendFrame(
                                ErrorFrameFlyweight.encode(
                                    allocator, 0, errorFrameThrowable(remoteImpliedPos)))
                            .then(Mono.fromRunnable(resumableConnection::dispose))
                            /*Resumption is impossible: no need to return control to ResumableConnection*/
                            .then(Mono.never())));
    return this;
  }

  public ClientRSocketSession resumeToken(ByteBuf resumeToken) {
    /*retain so token is not released once sent as part of setup frame*/
    this.resumeToken = resumeToken.retain();
    return this;
  }

  @Override
  public void reconnect(DuplexConnection connection) {
    resumableConnection.reconnect(connection);
  }

  @Override
  public ResumableDuplexConnection resumableConnection() {
    return resumableConnection;
  }

  @Override
  public ByteBuf token() {
    return resumeToken;
  }

  private Mono sendFrame(ByteBuf frame) {
    return resumableConnection.sendOne(frame).onErrorResume(err -> Mono.empty());
  }

  private static long remoteImpliedPos(ByteBuf resumeOkFrame) {
    return ResumeOkFrameFlyweight.lastReceivedClientPos(resumeOkFrame);
  }

  private static long remotePos(ByteBuf resumeOkFrame) {
    return -1;
  }

  private static ConnectionErrorException errorFrameThrowable(long impliedPos) {
    return new ConnectionErrorException("resumption_server_pos=[" + impliedPos + "]");
  }

  private static class RetrySignal implements Retry.RetrySignal {

    private final Throwable ex;

    RetrySignal(Throwable ex) {
      this.ex = ex;
    }

    @Override
    public long totalRetries() {
      return 0;
    }

    @Override
    public long totalRetriesInARow() {
      return 0;
    }

    @Override
    public Throwable failure() {
      return ex;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy