blob: a9d5ce5996b629dd3de946673bd1ffb6906c282c [file] [log] [blame]
/**********************************************************************
// @@@ START COPYRIGHT @@@
//
// 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.
//
// @@@ END COPYRIGHT @@@
**********************************************************************/
#ifndef EXSM_QUEUE_H
#define EXSM_QUEUE_H
#include "ExSMCommon.h"
class NAMemory;
//--------------------------------------------------------------------------
// Notes on class ExSMQueue
//--------------------------------------------------------------------------
// ExSMQueue provides a fixed-size, lock-free queue
//
// Most of the code in this class is copied from class ex_queue
//
// There is exactly one reader and one writer
//
// The reader never writes, the writer never reads
//
// Queue elements are stored in a circular array
//
// Two indexes head_ and tail_ track the head and tail entries
//
// head_ and tail_ are allowed to wrap around
//
// The queue is empty when head_ == tail_
//
// The number of entries is (tail_ - head_). If tail_ happens to
// wraparound before head_, we assume hardware arithmetic still
// reports the correct number of entries. This assumption also exists
// in class ex_queue.
//
// The maximum number of entries that can be stored is (size_ - 1)
//
// If we tried to store size_ elements we would have head_ == tail_
// when the queue is full and this violates the rule that the queue is
// empty when head_ == tail_.
//
// size_ is always a power of 2. This makes modulo arithmetic fast.
//
// The ex_queue protocol must be followed when adding or removing:
//
// Before adding, make sure the queue is not full
//
// Before removing, make sure the queue is not empty
//
// First the caller modifies a queue element and then
// does the add or remove. The add or remove only does
// an increment of head_ or tail_
//
// For lock-free thread safety a compiler memory barrier is added to
// the add/remove methods:
//
// The lock-free protocol requires that the following two steps are
// never reordered by gcc:
//
// 1. Modification of a queue element
// 2. Increment of the data member tail_ (for insert) or
// head_ (for remove)
//
// Without a compiler memory barrier, steps 1 and 2 could be
// reordered. The compiler memory barrier is implemented with the
// following gcc directive:
//
// asm volatile("" : : : "memory");
class ExSMQueue
{
public:
class Entry
{
public:
void *getData() { return data_; }
void setData(void *data) { data_ = data; }
protected:
Entry() {}
virtual ~Entry() {}
void *data_;
};
ExSMQueue(uint32_t initialSize, NAMemory *heap);
ExSMQueue(); // Do not implement
// If the queue is used to store pointers, this destructor will not
// delete the objects pointed to. The user of queue is responsible
// for deleting objects before calling this destructor.
virtual ~ExSMQueue();
Entry &getTailEntry() const
{
return queue_[tail_ & mask_];
}
uint32_t getTailIndex() const
{
return tail_;
}
void insert()
{
// We assert that queue is never full when insert() is called. The
// assert will only fail if the caller does not test isFull()
// before calling insert().
exsm_assert(!isFull(), "insert called when queue is full");
asm volatile("" : : : "memory");
tail_++;
}
Entry &getQueueEntry(uint32_t i) const
{
exsm_assert(!isVacant(i), "getQueueEntry called for vacant entry");
return queue_[i & mask_];
}
Entry &getHeadEntry() const
{
exsm_assert(!isEmpty(), "getHeadEntry called for empty queue");
return queue_[head_ & mask_];
}
uint32_t getHeadIndex() const
{
return head_;
}
bool entryExists(uint32_t i) const
{
return (!isVacant(i));
}
void removeHead()
{
// We assert that queue is never empty when removeHead() is
// called. The assert will only fail if the caller does not test
// isEmpty() before calling removeHead().
exsm_assert(!isEmpty(), "removeHead called for empty queue");
asm volatile("" : : : "memory");
head_++;
}
uint32_t getSize() const { return size_; }
uint32_t getLength() const { return tail_ - head_; }
bool isFull() const { return (getLength() == mask_); }
bool isEmpty() const { return (head_ == tail_); }
bool isVacant(uint32_t i) const
{
// We have two cases to consider
// * There is no wraparound and head_ <= tail_
// * tail_ has wrapped around before head_
if (head_ <= tail_)
return !((head_ <= i) && (i < tail_));
else
return !((head_ <= i) || (i < tail_));
}
private:
uint32_t head_;
uint32_t tail_;
uint32_t size_;
// Using a bit mask is faster than masking with (size_ - 1).
uint32_t mask_;
// Pointer to an array of queue entries
Entry *queue_;
NAMemory *heap_;
}; // class ExSMQueue
#endif // EXSM_QUEUE_H