/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.activemq.artemis.core.persistence.impl.journal;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.core.journal.EncodingSupport;
import org.apache.activemq.artemis.core.persistence.StorageManager;
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
import org.apache.activemq.artemis.utils.DataConstants;
import org.apache.activemq.artemis.utils.IDGenerator;
import org.jboss.logging.Logger;
/**
* An ID generator that allocates a batch of IDs of size {@link #checkpointSize} and records the ID
* in the journal only when starting a new batch.
*
* @see IDGenerator
*/
public final class BatchingIDGenerator implements IDGenerator {
private static final Logger logger = Logger.getLogger(BatchingIDGenerator.class);
private final AtomicLong counter;
private final long checkpointSize;
private volatile long nextID;
private final StorageManager storageManager;
private List<Long> cleanupRecords = null;
public BatchingIDGenerator(final long start, final long checkpointSize, final StorageManager storageManager) {
counter = new AtomicLong(start);
// as soon as you generate the first ID, the nextID should be updated
nextID = start;
this.checkpointSize = checkpointSize;
this.storageManager = storageManager;
}
public void persistCurrentID() {
final long recordID = counter.incrementAndGet();
storeID(recordID, recordID);
}
/**
* A method to cleanup old records after started
*/
public void cleanup() {
if (cleanupRecords != null) {
Iterator<Long> iterRecord = cleanupRecords.iterator();
while (iterRecord.hasNext()) {
Long record = iterRecord.next();
if (iterRecord.hasNext()) {
// we don't want to remove the last record
deleteID(record.longValue());
}
}
cleanupRecords.clear(); // help GC
cleanupRecords = null;
}
}
public void loadState(final long journalID, final ActiveMQBuffer buffer) {
addCleanupRecord(journalID);
IDCounterEncoding encoding = new IDCounterEncoding();
encoding.decode(buffer);
// Keep nextID and counter the same, the next generateID will update the checkpoint
nextID = encoding.id + 1;
counter.set(nextID);
}
// for testcases
public void forceNextID(long nextID) {
long idJournal = counter.incrementAndGet();
counter.set(nextID);
storeID(idJournal, nextID);
}
@Override
public long generateID() {
long id = counter.getAndIncrement();
if (id >= nextID) {
saveCheckPoint(id);
}
return id;
}
@Override
public long getCurrentID() {
return counter.get();
}
private synchronized void saveCheckPoint(final long id) {
if (id >= nextID) {
nextID += checkpointSize;
if (!storageManager.isStarted()) {
// This could happen after the server is stopped
// while notifications are being sent and ID generated.
// If the ID is intended to the journal you would know soon enough
// so we just ignore this for now
logger.debug("The journalStorageManager is not loaded. " + "This is probably ok as long as it's a notification being sent after shutdown");
} else {
storeID(counter.getAndIncrement(), nextID);
}
}
}
private void addCleanupRecord(long id) {
if (cleanupRecords == null) {
cleanupRecords = new LinkedList<>();
}
cleanupRecords.add(id);
}
private void storeID(final long journalID, final long id) {
try {
storageManager.storeID(journalID, id);
} catch (Exception e) {
ActiveMQServerLogger.LOGGER.batchingIdError(e);
}
}
private void deleteID(final long journalID) {
try {
storageManager.deleteID(journalID);
} catch (Exception e) {
ActiveMQServerLogger.LOGGER.batchingIdError(e);
}
}
public static EncodingSupport createIDEncodingSupport(final long id) {
return new IDCounterEncoding(id);
}
// Inner classes -------------------------------------------------
protected static final class IDCounterEncoding implements EncodingSupport {
private long id;
@Override
public String toString() {
return "IDCounterEncoding [id=" + id + "]";
}
private IDCounterEncoding(final long id) {
this.id = id;
}
IDCounterEncoding() {
}
@Override
public void decode(final ActiveMQBuffer buffer) {
id = buffer.readLong();
}
@Override
public void encode(final ActiveMQBuffer buffer) {
buffer.writeLong(id);
}
@Override
public int getEncodeSize() {
return DataConstants.SIZE_LONG;
}
}
}