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

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

There is a newer version: 1.1.4
Show newest version
/*
 * Copyright 2015-2019 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 static io.rsocket.resume.ResumableDuplexConnection.isResumableFrame;

import io.netty.buffer.ByteBuf;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import org.reactivestreams.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.CoreSubscriber;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoProcessor;
import reactor.core.publisher.Operators;

/**
 * writes - n (where n is frequent, primary operation) reads - m (where m == KeepAliveFrequency)
 * skip - k -> 0 (where k is the rare operation which happens after disconnection
 */
public class InMemoryResumableFramesStore extends Flux
    implements CoreSubscriber, ResumableFramesStore, Subscription {

  private static final Logger logger = LoggerFactory.getLogger(InMemoryResumableFramesStore.class);

  final MonoProcessor disposed = MonoProcessor.create();
  final ArrayList cachedFrames;
  final String tag;
  final int cacheLimit;

  volatile long impliedPosition;
  static final AtomicLongFieldUpdater IMPLIED_POSITION =
      AtomicLongFieldUpdater.newUpdater(InMemoryResumableFramesStore.class, "impliedPosition");

  volatile long position;
  static final AtomicLongFieldUpdater POSITION =
      AtomicLongFieldUpdater.newUpdater(InMemoryResumableFramesStore.class, "position");

  volatile int cacheSize;
  static final AtomicIntegerFieldUpdater CACHE_SIZE =
      AtomicIntegerFieldUpdater.newUpdater(InMemoryResumableFramesStore.class, "cacheSize");

  CoreSubscriber saveFramesSubscriber;

  CoreSubscriber actual;

  /**
   * Indicates whether there is an active connection or not.
   *
   * 
    *
  • 0 - no active connection *
  • 1 - active connection *
  • 2 - disposed *
* *
   * 0 <-----> 1
   * |         |
   * +--> 2 <--+
   * 
*/ volatile int state; static final AtomicIntegerFieldUpdater STATE = AtomicIntegerFieldUpdater.newUpdater(InMemoryResumableFramesStore.class, "state"); public InMemoryResumableFramesStore(String tag, int cacheSizeBytes) { this.tag = tag; this.cacheLimit = cacheSizeBytes; this.cachedFrames = new ArrayList<>(); } public Mono saveFrames(Flux frames) { return frames .transform( Operators.lift( (__, actual) -> { this.saveFramesSubscriber = actual; return this; })) .then(); } @Override public void releaseFrames(long remoteImpliedPos) { long pos = position; logger.debug( "{} Removing frames for local: {}, remote implied: {}", tag, pos, remoteImpliedPos); long toRemoveBytes = Math.max(0, remoteImpliedPos - pos); int removedBytes = 0; final ArrayList frames = cachedFrames; synchronized (this) { while (toRemoveBytes > removedBytes && frames.size() > 0) { ByteBuf cachedFrame = frames.remove(0); int frameSize = cachedFrame.readableBytes(); cachedFrame.release(); removedBytes += frameSize; } } if (toRemoveBytes > removedBytes) { throw new IllegalStateException( String.format( "Local and remote state disagreement: " + "need to remove additional %d bytes, but cache is empty", toRemoveBytes)); } else if (toRemoveBytes < removedBytes) { throw new IllegalStateException( "Local and remote state disagreement: local and remote frame sizes are not equal"); } else { POSITION.addAndGet(this, removedBytes); if (cacheLimit != Integer.MAX_VALUE) { CACHE_SIZE.addAndGet(this, -removedBytes); logger.debug("{} Removed frames. Current cache size: {}", tag, cacheSize); } } } @Override public Flux resumeStream() { return this; } @Override public long framePosition() { return position; } @Override public long frameImpliedPosition() { return impliedPosition & Long.MAX_VALUE; } @Override public boolean resumableFrameReceived(ByteBuf frame) { final int frameSize = frame.readableBytes(); for (; ; ) { final long impliedPosition = this.impliedPosition; if (impliedPosition < 0) { return false; } if (IMPLIED_POSITION.compareAndSet(this, impliedPosition, impliedPosition + frameSize)) { return true; } } } void pauseImplied() { for (; ; ) { final long impliedPosition = this.impliedPosition; if (IMPLIED_POSITION.compareAndSet(this, impliedPosition, impliedPosition | Long.MIN_VALUE)) { logger.debug("Tag {}. Paused at position[{}]", tag, impliedPosition); return; } } } void resumeImplied() { for (; ; ) { final long impliedPosition = this.impliedPosition; final long restoredImpliedPosition = impliedPosition & Long.MAX_VALUE; if (IMPLIED_POSITION.compareAndSet(this, impliedPosition, restoredImpliedPosition)) { logger.debug("Tag {}. Resumed at position[{}]", tag, restoredImpliedPosition); return; } } } @Override public Mono onClose() { return disposed; } @Override public void dispose() { if (STATE.getAndSet(this, 2) != 2) { cacheSize = 0; synchronized (this) { logger.debug("Tag {}.Disposing InMemoryFrameStore", tag); for (ByteBuf frame : cachedFrames) { if (frame != null) { frame.release(); } } cachedFrames.clear(); } disposed.onComplete(); } } @Override public boolean isDisposed() { return state == 2; } @Override public void onSubscribe(Subscription s) { saveFramesSubscriber.onSubscribe(Operators.emptySubscription()); s.request(Long.MAX_VALUE); } @Override public void onError(Throwable t) { saveFramesSubscriber.onError(t); } @Override public void onComplete() { saveFramesSubscriber.onComplete(); } @Override public void onNext(ByteBuf frame) { final int state; final boolean isResumable = isResumableFrame(frame); if (isResumable) { final ArrayList frames = cachedFrames; int incomingFrameSize = frame.readableBytes(); final int cacheLimit = this.cacheLimit; if (cacheLimit != Integer.MAX_VALUE) { long availableSize = cacheLimit - cacheSize; if (availableSize < incomingFrameSize) { int removedBytes = 0; synchronized (this) { while (availableSize < incomingFrameSize) { if (frames.size() == 0) { break; } ByteBuf cachedFrame; cachedFrame = frames.remove(0); final int frameSize = cachedFrame.readableBytes(); availableSize += frameSize; removedBytes += frameSize; cachedFrame.release(); } } CACHE_SIZE.addAndGet(this, -removedBytes); POSITION.addAndGet(this, removedBytes); } } synchronized (this) { state = this.state; if (state != 2) { frames.add(frame); } } if (cacheLimit != Integer.MAX_VALUE) { CACHE_SIZE.addAndGet(this, incomingFrameSize); } } else { state = this.state; } final CoreSubscriber actual = this.actual; if (state == 1) { actual.onNext(frame.retain()); } else if (!isResumable || state == 2) { frame.release(); } } @Override public void request(long n) {} @Override public void cancel() { pauseImplied(); state = 0; } @Override public void subscribe(CoreSubscriber actual) { final int state = this.state; if (state != 2) { resumeImplied(); logger.debug( "Tag: {}. Subscribed at Position[{}] and ImpliedPosition[{}]", tag, position, impliedPosition); actual.onSubscribe(this); synchronized (this) { for (final ByteBuf frame : cachedFrames) { actual.onNext(frame.retain()); } } this.actual = actual; STATE.compareAndSet(this, 0, 1); } else { Operators.complete(actual); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy