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

com.hazelcast.ringbuffer.impl.RingbufferContainer Maven / Gradle / Ivy

/*
 * Copyright (c) 2008-2016, Hazelcast, Inc. All Rights Reserved.
 *
 * 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 com.hazelcast.ringbuffer.impl;

import com.hazelcast.config.InMemoryFormat;
import com.hazelcast.config.RingbufferConfig;
import com.hazelcast.nio.ObjectDataInput;
import com.hazelcast.nio.ObjectDataOutput;
import com.hazelcast.nio.serialization.Data;
import com.hazelcast.nio.serialization.DataSerializable;
import com.hazelcast.ringbuffer.StaleSequenceException;
import com.hazelcast.spi.NodeEngine;
import com.hazelcast.spi.serialization.SerializationService;

import java.io.IOException;

import static com.hazelcast.config.InMemoryFormat.BINARY;
import static com.hazelcast.config.InMemoryFormat.OBJECT;
import static com.hazelcast.config.InMemoryFormat.values;
import static com.hazelcast.util.Clock.currentTimeMillis;
import static java.util.concurrent.TimeUnit.SECONDS;


/**
 * The RingbufferContainer is responsible for storing the actual content of a ringbuffer.
 * 

* Currently the Ringbuffer is not a partitioned data-structure. So all data of a ringbuffer is stored in a single partition * and replicated to the replica's. No thread-safety is needed since a partition can only be accessed by a single thread at * any given moment. *

* The ringItems is the ring that contains the actual items. * The ringExpiration contains the expiration time of an item. * The if a time to live is set, the ringExpiration is created. Otherwise it is null to safe space. * The expiration time of an item can be found at the same index as the item itself. So these 2 arrays are always in step with * each other. * The reason why 2 array are created instead of just wrapping the item in a new object containing the expiration is that * we don't want to generate more waste than needed. */ public class RingbufferContainer implements DataSerializable { private static final long TTL_DISABLED = 0; // contains the actual items Object[] ringItems; // contains the expiration time in ms when the item should be expired. long[] ringExpirationMs; InMemoryFormat inMemoryFormat; long ttlMs; RingbufferConfig config; String name; long tailSequence = -1; long headSequence = tailSequence + 1; int capacity; // a cached version of the wait notify key needed to wait for a change if the ringbuffer is empty private final RingbufferWaitNotifyKey emptyRingWaitNotifyKey; private SerializationService serializationService; public RingbufferContainer(String name) { this.name = name; this.emptyRingWaitNotifyKey = new RingbufferWaitNotifyKey(name, "empty"); } public RingbufferContainer(RingbufferConfig config, SerializationService serializationService) { this(config.getName(), config, serializationService); } public RingbufferContainer(String name, RingbufferConfig config, SerializationService serializationService) { this(name); this.serializationService = serializationService; this.config = config; this.capacity = config.getCapacity(); this.inMemoryFormat = config.getInMemoryFormat(); this.ringItems = new Object[capacity]; this.ttlMs = SECONDS.toMillis(config.getTimeToLiveSeconds()); if (isTTLEnabled()) { ringExpirationMs = new long[capacity]; } } public void init(NodeEngine nodeEngine) { this.config = nodeEngine.getConfig().getRingbufferConfig(name); this.serializationService = nodeEngine.getSerializationService(); } public RingbufferWaitNotifyKey getRingEmptyWaitNotifyKey() { return emptyRingWaitNotifyKey; } public RingbufferConfig getConfig() { return config; } public long tailSequence() { return tailSequence; } public long headSequence() { return headSequence; } // just for testing public void setHeadSequence(long sequence) { headSequence = sequence; } public int getCapacity() { return capacity; } public long size() { return tailSequence - headSequence + 1; } public boolean isEmpty() { return size() == 0; } public boolean shouldWait(long sequence) { checkBlockableReadSequence(sequence); return sequence == tailSequence + 1; } public long remainingCapacity() { if (isTTLEnabled()) { return capacity - size(); } return capacity; } private boolean isTTLEnabled() { return ttlMs != TTL_DISABLED; } int toIndex(long sequence) { return (int) (sequence % ringItems.length); } void checkReadSequence(long sequence) { if (sequence > tailSequence) { throw new IllegalArgumentException("sequence:" + sequence + " is too large. The current tailSequence is:" + tailSequence); } if (sequence < headSequence) { throw new StaleSequenceException("sequence:" + sequence + " is too small. The current headSequence is:" + headSequence + " tailSequence is:" + tailSequence, headSequence); } } public void checkBlockableReadSequence(long readSequence) { if (readSequence > tailSequence + 1) { throw new IllegalArgumentException("sequence:" + readSequence + " is too large. The current tailSequence is:" + tailSequence); } if (readSequence < headSequence) { throw new StaleSequenceException("sequence:" + readSequence + " is too small. The current headSequence is:" + headSequence + " tailSequence is:" + tailSequence, headSequence); } } public long add(Data item) { return addInternal(item); } private long addInternal(Data dataItem) { tailSequence++; if (tailSequence - capacity == headSequence) { headSequence++; } int index = toIndex(tailSequence); Object item = dataItem; if (inMemoryFormat == OBJECT) { item = serializationService.toObject(dataItem); } // first we write the dataItem in the ring. ringItems[index] = item; // and then we optionally write the expiration. if (isTTLEnabled()) { ringExpirationMs[index] = currentTimeMillis() + ttlMs; } return tailSequence; } public long addAll(Data[] items) { long result = -1; for (Data item : items) { result = addInternal(item); } return result; } public Data read(long sequence) { checkReadSequence(sequence); int index = toIndex(sequence); Object item = ringItems[index]; return serializationService.toData(item); } /** * @param beginSequence the sequence of the first item to read. * @param result the List where the result are stored in. * @return returns the sequenceId of the next item to read. This is needed if not all required items are found. */ public long readMany(long beginSequence, ReadResultSetImpl result) { checkReadSequence(beginSequence); long seq = beginSequence; while (seq <= tailSequence) { int index = toIndex(seq); Object item = ringItems[index]; result.addItem(item); seq++; if (result.isMaxSizeReached()) { // we have found all items we are looking for. We are done. break; } } return seq; } /** * Cleans up the ringbuffer by deleting all expired items. */ public void cleanup() { if (!isTTLEnabled() || headSequence > tailSequence) { return; } long now = currentTimeMillis(); while (headSequence <= tailSequence) { int index = toIndex(headSequence); if (ringExpirationMs[index] > now) { return; } // we null the slot and allow the gc to take care of the object. // if we don't clean it, we'll have a potential memory leak. ringItems[index] = null; // we don't need to 0 the ringExpirationMs slot since it contains a long value. // and we move the head to the next item. // if nothing remains in the ringbuffer, then the head will be 1 larger than the tail. headSequence++; } } @Override public void writeData(ObjectDataOutput out) throws IOException { out.writeLong(tailSequence); out.writeLong(headSequence); out.writeInt(capacity); out.writeLong(ttlMs); out.writeInt(inMemoryFormat.ordinal()); boolean ttlEnabled = isTTLEnabled(); long now = System.currentTimeMillis(); // we only write the actual content of the ringbuffer. So we don't write empty slots. for (long seq = headSequence; seq <= tailSequence; seq++) { int index = toIndex(seq); if (inMemoryFormat == BINARY) { out.writeData((Data) ringItems[index]); } else { out.writeObject(ringItems[index]); } // we write the time difference compared to now. Because the clock on the receiving side // can be totally different then our time. If there would be a ttl of 10 seconds, than // the expiration time would be 10 seconds after the insertion time. But the if ringbuffer is // migrated to a machine with a time 1 hour earlier, the ttl would effectively become // 1 hours and 10 seconds. if (ttlEnabled) { long deltaMs = ringExpirationMs[index] - now; out.writeLong(deltaMs); } } } @Override public void readData(ObjectDataInput in) throws IOException { tailSequence = in.readLong(); headSequence = in.readLong(); capacity = in.readInt(); ttlMs = in.readLong(); inMemoryFormat = values()[in.readInt()]; ringItems = new Object[capacity]; boolean ttlEnabled = isTTLEnabled(); if (ttlEnabled) { ringExpirationMs = new long[capacity]; } long now = System.currentTimeMillis(); for (long seq = headSequence; seq <= tailSequence; seq++) { int index = toIndex(seq); if (inMemoryFormat == BINARY) { ringItems[index] = in.readData(); } else { ringItems[index] = in.readObject(); } if (ttlEnabled) { long delta = in.readLong(); ringExpirationMs[index] = delta + now; } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy