/* * Copyright (c) 2015 Pantheon Technologies s.r.o. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.openflowjava.protocol.impl.core.connection; import com.google.common.base.FinalizableReferenceQueue; import com.google.common.base.FinalizableSoftReference; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import com.google.common.base.Verify; import java.lang.ref.Reference; import java.util.concurrent.ConcurrentLinkedDeque; import org.opendaylight.openflowjava.protocol.api.connection.DeviceRequestFailedException; import org.opendaylight.openflowjava.protocol.api.connection.OutboundQueueException; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.Error; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.OfHeader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; final class StackedSegment { private static final class QueueRef extends FinalizableSoftReference<OutboundQueueEntry[]> { QueueRef(final FinalizableReferenceQueue queue, final OutboundQueueEntry[] referent) { super(referent, queue); } @Override public void finalizeReferent() { CACHE.remove(this); } } /** * Size of each individual segment */ static final int SEGMENT_SIZE = 4096; private static final Logger LOG = LoggerFactory.getLogger(StackedSegment.class); private static final FinalizableReferenceQueue REF_QUEUE = new FinalizableReferenceQueue(); private static final ConcurrentLinkedDeque<QueueRef> CACHE = new ConcurrentLinkedDeque<>(); private final OutboundQueueEntry[] entries; private final long baseXid; private final long endXid; // Updated from netty only private int lastBarrierOffset = -1; private int completeCount; StackedSegment(final long baseXid, final OutboundQueueEntry[] entries) { this.baseXid = baseXid; this.endXid = baseXid + SEGMENT_SIZE; this.entries = Preconditions.checkNotNull(entries); } static StackedSegment create(final long baseXid) { final StackedSegment ret; for (;;) { final Reference<OutboundQueueEntry[]> item = CACHE.pollLast(); if (item == null) { break; } final OutboundQueueEntry[] cached = item.get(); if (cached != null) { ret = new StackedSegment(baseXid, cached); LOG.trace("Reusing array {} in segment {}", cached, ret); return ret; } } final OutboundQueueEntry[] entries = new OutboundQueueEntry[SEGMENT_SIZE]; for (int i = 0; i < SEGMENT_SIZE; ++i) { entries[i] = new OutboundQueueEntry(); } ret = new StackedSegment(baseXid, entries); LOG.trace("Allocated new segment {}", ret); return ret; } @Override public String toString() { return MoreObjects.toStringHelper(this).add("baseXid", baseXid).add("endXid", endXid).add("completeCount", completeCount).toString(); } long getBaseXid() { return baseXid; } long getEndXid() { return endXid; } OutboundQueueEntry getEntry(final int offset) { return entries[offset]; } private boolean xidInRange(final long xid) { return xid < endXid && (xid >= baseXid || baseXid > endXid); } private static boolean completeEntry(final OutboundQueueEntry entry, final OfHeader response) { if (response instanceof Error) { final Error err = (Error)response; LOG.debug("Device-reported request XID {} failed {}:{}", response.getXid(), err.getTypeString(), err.getCodeString()); entry.fail(new DeviceRequestFailedException("Device-side failure", err)); return true; } return entry.complete(response); } OutboundQueueEntry findEntry(final long xid) { if (! xidInRange(xid)) { LOG.debug("Queue {} {}/{} ignoring XID {}", this, baseXid, entries.length, xid); return null; } final int offset = (int)(xid - baseXid); return entries[offset]; } OutboundQueueEntry pairRequest(final OfHeader response) { // Explicitly 'long' to force unboxing before performing operations final long xid = response.getXid(); if (!xidInRange(xid)) { LOG.debug("Queue {} {}/{} ignoring XID {}", this, baseXid, entries.length, xid); return null; } final int offset = (int) (xid - baseXid); final OutboundQueueEntry entry = entries[offset]; if (entry.isCompleted()) { LOG.debug("Entry {} already is completed, not accepting response {}", entry, response); return null; } if (entry.isBarrier()) { // This has been a barrier -- make sure we complete all preceding requests. // XXX: Barriers are expected to complete in one message. // If this assumption is changed, this logic will need to be expanded // to ensure that the requests implied by the barrier are reported as // completed *after* the barrier. LOG.trace("Barrier XID {} completed, cascading completion to XIDs {} to {}", xid, baseXid + lastBarrierOffset + 1, xid - 1); completeRequests(offset); lastBarrierOffset = offset; final boolean success = completeEntry(entry, response); Verify.verify(success, "Barrier request failed to complete"); completeCount++; } else if (completeEntry(entry, response)) { completeCount++; } return entry; } private void completeRequests(final int toOffset) { for (int i = lastBarrierOffset + 1; i < toOffset; ++i) { final OutboundQueueEntry entry = entries[i]; if (!entry.isCompleted() && entry.complete(null)) { completeCount++; } } } void completeAll() { completeRequests(entries.length); } int failAll(final OutboundQueueException cause) { int ret = 0; for (int i = lastBarrierOffset + 1; i < entries.length; ++i) { final OutboundQueueEntry entry = entries[i]; if (!entry.isCommitted()) { break; } if (!entry.isCompleted()) { entry.fail(cause); completeCount++; ret++; } } return ret; } boolean isComplete() { return completeCount >= entries.length; } void recycle() { for (final OutboundQueueEntry e : entries) { e.reset(); } CACHE.offer(new QueueRef(REF_QUEUE, entries)); } }