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

com.github.benmanes.caffeine.cache.WriteBuffer Maven / Gradle / Ivy

There is a newer version: 4.15.102
Show newest version
/*
 * 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.github.benmanes.caffeine.cache;

import static com.github.benmanes.caffeine.cache.BoundedLocalCache.ceilingPowerOfTwo;
import static java.util.Objects.requireNonNull;

import com.github.benmanes.caffeine.base.UnsafeAccess;
import com.github.benmanes.caffeine.cache.WBHeader.ConsumerRef;

/**
 * An MPSC array queue which starts at initialCapacity and grows to maxCapacity in
 * linked chunks of the initial size. The queue grows only when the current buffer is full and
 * elements are not copied on resize, instead a link to the new buffer is stored in the old buffer
 * for the consumer to follow.
 * 

* This is a trimmed down version of MpscChunkedArrayQueue provided by * JCTools. * * @author [email protected] (Nitsan Wakart) * @author [email protected] (Ben Manes) */ @SuppressWarnings("restriction") final class WriteBuffer extends ConsumerRef { long p60, p61, p62, p63, p64, p65, p66, p67; long p70, p71, p72, p73, p74, p75, p76; static final long ARRAY_BASE = UnsafeAccess.UNSAFE.arrayBaseOffset(Object[].class); static final Object JUMP = new Object(); static final int ELEMENT_SHIFT; /** * @param initialCapacity the queue initial capacity. If chunk size is fixed this will be the * chunk size. Must be 2 or more. * @param maxCapacity the maximum capacity will be rounded up to the closest power of 2 and will * be the upper limit of number of elements in this queue. Must be 4 or more and round up * to a larger power of 2 than initialCapacity. */ public WriteBuffer(int initialCapacity, int maxCapacity) { Caffeine.requireArgument(maxCapacity > 4); Caffeine.requireArgument(initialCapacity > 2); if (ceilingPowerOfTwo(initialCapacity) >= ceilingPowerOfTwo(maxCapacity)) { throw new IllegalArgumentException( "Initial capacity cannot exceed maximum capacity(both rounded up to a power of 2)"); } int p2capacity = ceilingPowerOfTwo(initialCapacity); // leave lower bit of mask clear long mask = (p2capacity - 1) << 1; // need extra element to point at next array @SuppressWarnings("unchecked") E[] buffer = (E[]) new Object[p2capacity + 1]; producerBuffer = buffer; producerMask = mask; consumerBuffer = buffer; consumerMask = mask; maxQueueCapacity = ceilingPowerOfTwo(maxCapacity) << 1; soProducerLimit(mask); // we know it's all empty to start with } @SuppressWarnings({"PMD.SwitchStmtsShouldHaveDefault", "PMD.MissingBreakInSwitch"}) public boolean offer(E e) { requireNonNull(e); long mask; E[] buffer; long producerIndex; for (;;) { long producerLimit = lvProducerLimit(); producerIndex = lvProducerIndex(); // lower bit is indicative of resize, if we see it we spin until it's cleared if ((producerIndex & 1) == 1) { continue; } // producerIndex is even (lower bit is 0) -> actual index is (producerIndex >> 1) // mask/buffer may get changed by resizing -> only use for array access after successful CAS mask = producerMask; buffer = producerBuffer; // a successful CAS ties the ordering, lv(producerIndex)-[mask/buffer]->cas(producerIndex) // assumption behind this optimization is that queue is almost always empty or near empty if (producerLimit <= producerIndex) { int result = offerSlowPath(e, mask, buffer, producerIndex, producerLimit); switch (result) { case 0: break; case 1: continue; case 2: return false; case 3: return true; } } if (casProducerIndex(producerIndex, producerIndex + 2)) { break; } } long offset = modifiedCalcElementOffset(producerIndex, mask); UnsafeAccess.UNSAFE.putOrderedObject(buffer, offset, e); return true; } private int offerSlowPath(E e, long mask, E[] buffer, long producerIndex, long producerLimit) { int result; long consumerIndex = lvConsumerIndex(); long maxQueueCapacity = this.maxQueueCapacity; long bufferCapacity = ((mask + 2) == maxQueueCapacity) ? maxQueueCapacity : mask; if (consumerIndex + bufferCapacity > producerIndex) { if (!casProducerLimit(producerLimit, consumerIndex + bufferCapacity)) { result = 1;// retry from top } result = 0;// 0 - goto producerIndex CAS } // full and cannot grow else if (consumerIndex == (producerIndex - maxQueueCapacity)) { result = 2;// -> return false; } // grab index for resize -> set lower bit else if (casProducerIndex(producerIndex, producerIndex + 1)) { // resize will adjust the consumerIndexCache int newBufferLength = buffer.length; if (buffer.length - 1 == maxQueueCapacity) { throw new IllegalStateException(); } newBufferLength = 2 * buffer.length - 1; @SuppressWarnings("unchecked") E[] newBuffer = (E[]) new Object[newBufferLength]; producerBuffer = newBuffer; producerMask = (newBufferLength - 2) << 1; long offsetInOld = modifiedCalcElementOffset(producerIndex, mask); long offsetInNew = modifiedCalcElementOffset(producerIndex, producerMask); UnsafeAccess.UNSAFE.putOrderedObject(newBuffer, offsetInNew, e); UnsafeAccess.UNSAFE.putOrderedObject(buffer, nextArrayOffset(mask), newBuffer); long available = maxQueueCapacity - (producerIndex - consumerIndex); if (available <= 0) { throw new IllegalStateException(); } // invalidate racing CASs soProducerLimit(producerIndex + Math.min(mask, available)); // make resize visible to consumer UnsafeAccess.UNSAFE.putOrderedObject(buffer, offsetInOld, JUMP); // make resize visible to the other producers soProducerIndex(producerIndex + 2); result = 3;// -> return true } else { result = 1;// failed resize attempt, retry from top } return result; } @SuppressWarnings("PMD.ConfusingTernary") public E poll() { E[] buffer = consumerBuffer; long index = consumerIndex; long mask = consumerMask; long offset = modifiedCalcElementOffset(index, mask); Object e = lvElement(buffer, offset);// LoadLoad if (e == null) { if (index != lvProducerIndex()) { // poll() == null iff queue is empty, null element is not strong enough indicator, so we // must check the producer index. If the queue is indeed not empty we spin until element is // visible. do { e = lvElement(buffer, offset); } while (e == null); } else { return null; } } if (e == JUMP) { final E[] nextBuffer = getNextBuffer(buffer, mask); return newBufferPoll(nextBuffer, index); } soElement(buffer, offset, null); soConsumerIndex(index + 2); @SuppressWarnings("unchecked") E casted = (E) e; return casted; } public int size() { /* * It is possible for a thread to be interrupted or reschedule between the read of the producer * and consumer indices, therefore protection is required to ensure size is within valid range. * In the event of concurrent polls/offers to this method the size is OVER estimated as we read * consumer index BEFORE the producer index. */ long after = lvConsumerIndex(); for (;;) { long before = after; long currentProducerIndex = lvProducerIndex(); after = lvConsumerIndex(); if (before == after) { return (int) (currentProducerIndex - after) >> 1; } } } /** * This method assumes index is actually (index << 1) because lower bit is used for resize. This * is compensated for by reducing the element shift. The computation is constant folded, so * there's no cost. */ private static long modifiedCalcElementOffset(long index, long mask) { return ARRAY_BASE + ((index & mask) << (ELEMENT_SHIFT - 1)); } @SuppressWarnings("unchecked") private E[] getNextBuffer(E[] buffer, long mask) { long nextArrayOffset = nextArrayOffset(mask); E[] nextBuffer = (E[]) UnsafeAccess.UNSAFE.getObjectVolatile(buffer, nextArrayOffset); UnsafeAccess.UNSAFE.putOrderedObject(buffer, nextArrayOffset, null); return nextBuffer; } private long nextArrayOffset(long mask) { return modifiedCalcElementOffset(mask + 2, Long.MAX_VALUE); } @SuppressWarnings("unchecked") private E newBufferPoll(E[] nextBuffer, long index) { long offsetInNew = newBufferAndOffset(nextBuffer, index); E n = (E) UnsafeAccess.UNSAFE.getObjectVolatile(nextBuffer, offsetInNew);// LoadLoad if (n == null) { throw new IllegalStateException("new buffer must have at least one element"); } UnsafeAccess.UNSAFE.putOrderedObject(nextBuffer, offsetInNew, null);// StoreStore soConsumerIndex(index + 2); return n; } @SuppressWarnings("PMD.ArrayIsStoredDirectly") private long newBufferAndOffset(E[] nextBuffer, long index) { consumerBuffer = nextBuffer; consumerMask = (nextBuffer.length - 2) << 1; long offsetInNew = modifiedCalcElementOffset(index, consumerMask); return offsetInNew; } @SuppressWarnings("unchecked") public static E lvElement(E[] buffer, long offset) { return (E) UnsafeAccess.UNSAFE.getObjectVolatile(buffer, offset); } public static void soElement(E[] buffer, long offset, E e) { UnsafeAccess.UNSAFE.putOrderedObject(buffer, offset, e); } private long lvProducerIndex() { return UnsafeAccess.UNSAFE.getLongVolatile(this, P_INDEX_OFFSET); } private long lvConsumerIndex() { return UnsafeAccess.UNSAFE.getLongVolatile(this, C_INDEX_OFFSET); } private void soProducerIndex(long v) { UnsafeAccess.UNSAFE.putOrderedLong(this, P_INDEX_OFFSET, v); } private boolean casProducerIndex(long expect, long newValue) { return UnsafeAccess.UNSAFE.compareAndSwapLong(this, P_INDEX_OFFSET, expect, newValue); } private void soConsumerIndex(long v) { UnsafeAccess.UNSAFE.putOrderedLong(this, C_INDEX_OFFSET, v); } private long lvProducerLimit() { return producerLimit; } private boolean casProducerLimit(long expect, long newValue) { return UnsafeAccess.UNSAFE.compareAndSwapLong(this, P_LIMIT_OFFSET, expect, newValue); } private void soProducerLimit(long v) { UnsafeAccess.UNSAFE.putOrderedLong(this, P_LIMIT_OFFSET, v); } static { int scale = UnsafeAccess.UNSAFE.arrayIndexScale(Object[].class); if (scale == 4) { ELEMENT_SHIFT = 2; } else if (scale == 8) { ELEMENT_SHIFT = 3; } else { throw new IllegalStateException("Unknown pointer size"); } } } /** The namespace for field padding through inheritance. */ final class WBHeader { @SuppressWarnings("PMD.AbstractClassWithoutAbstractMethod") static abstract class PadProducerIndex { long p00, p01, p02, p03, p04, p05, p06, p07; long p10, p11, p12, p13, p14, p15, p16; } static abstract class ProducerIndexRef extends PadProducerIndex { static final long P_INDEX_OFFSET = UnsafeAccess.objectFieldOffset(ProducerIndexRef.class, "producerIndex"); long producerIndex; } static abstract class PadColdProducer extends ProducerIndexRef { long p20, p21, p22, p23, p24, p25, p26, p27; long p30, p31, p32, p33, p34, p35, p36; } static abstract class ColdProducerRef extends PadColdProducer { static final long P_LIMIT_OFFSET = UnsafeAccess.objectFieldOffset(ColdProducerRef.class, "producerLimit"); long maxQueueCapacity; long producerMask; E[] producerBuffer; volatile long producerLimit; } static abstract class PadConsumer extends ColdProducerRef { long p40, p41, p42, p43, p44, p45, p46, p47; long p50, p51, p52, p53, p54, p55, p56; } static abstract class ConsumerRef extends PadConsumer { static final long C_INDEX_OFFSET = UnsafeAccess.objectFieldOffset(ConsumerRef.class, "consumerIndex"); long consumerMask; E[] consumerBuffer; long consumerIndex; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy