--- /dev/null
+/*
+ * $Id$
+ *
+ * DEBUG: section 54 Interprocess Communication
+ *
+ */
+
+#include "config.h"
+
+#include "base/TextException.h"
+#include "ipc/mem/PageStack.h"
+
+/// used to mark a stack slot available for storing free page offsets
+const Ipc::Mem::PageStack::Value Writable = 0;
+
+
+Ipc::Mem::PageStack::PageStack(const String &id, const unsigned int capacity):
+ shm(id.termedBuf())
+{
+ shm.create(sizeof(Shared::Item) * capacity + sizeof(Shared));
+ assert(shm.mem());
+ shared = new (shm.mem()) Shared(capacity);
+}
+
+Ipc::Mem::PageStack::PageStack(const String &id): shm(id.termedBuf())
+{
+ shm.open();
+ shared = reinterpret_cast<Shared *>(shm.mem());
+ assert(shared);
+}
+
+/*
+ * TODO: We currently rely on the theLastReadable hint during each
+ * loop iteration. We could also use hint just for the start position:
+ * (const Offset start = theLastReadable) and then scan the stack
+ * sequentially regardless of theLastReadable changes by others. Which
+ * approach is better? Same for push().
+ */
+bool
+Ipc::Mem::PageStack::pop(Value &value)
+{
+ // we may fail to dequeue, but be conservative to prevent long searches
+ --shared->theSize;
+
+ // find a Readable slot, starting with theLastReadable and going left
+ while (shared->theSize >= 0) {
+ const Offset idx = shared->theLastReadable;
+ // mark the slot at ids Writable while extracting its current value
+ value = shared->theItems[idx].fetchAndAnd(0); // works if Writable is 0
+ const bool popped = value != Writable;
+ // theItems[idx] is probably not Readable [any more]
+ // help others (if we popped) or go left ourselves (if we did not)
+ shared->theLastReadable.swap_if(idx, shared->prev(idx)); // may fail or lie
+ if (popped) {
+ // the slot we emptied may already be filled, but that is OK
+ shared->theFirstWritable = idx; // may lie
+ return true;
+ }
+ // TODO: report suspiciously long loops
+ }
+
+ ++shared->theSize;
+ return false;
+}
+
+void
+Ipc::Mem::PageStack::push(const Value value)
+{
+ Must(value != Writable);
+ // find a Writable slot, starting with theFirstWritable and going right
+ while (shared->theSize < shared->theCapacity) {
+ const Offset idx = shared->theFirstWritable;
+ const bool pushed = shared->theItems[idx].swap_if(Writable, value);
+ // theItems[idx] is probably not Writable [any more];
+ // help others (if we pushed) or go right ourselves (if we did not)
+ shared->theFirstWritable.swap_if(idx, shared->next(idx)); // may fail or lie
+ if (pushed) {
+ // the enqueued value may already by gone, but that is OK
+ shared->theLastReadable = idx; // may lie
+ ++shared->theSize;
+ return;
+ }
+ // TODO: report suspiciously long loops
+ }
+ Must(false); // the number of pages cannot exceed theCapacity
+}
+
+Ipc::Mem::PageStack::Shared::Shared(const unsigned int aCapacity):
+ theCapacity(aCapacity), theSize(theCapacity),
+ theLastReadable(prev(theSize)), theFirstWritable(next(theLastReadable))
+{
+ // initially, all pages are free
+ for (Offset i = 0; i < theSize; ++i)
+ theItems[i] = i + 1; // skip page number zero to keep numbers positive
+}
--- /dev/null
+/*
+ * $Id$
+ *
+ */
+
+#ifndef SQUID_IPC_MEM_PAGE_STACK_H
+#define SQUID_IPC_MEM_PAGE_STACK_H
+
+#include "ipc/AtomicWord.h"
+#include "ipc/SharedMemory.h"
+
+namespace Ipc {
+
+namespace Mem {
+
+/// Atomic container of "free" page numbers inside a single SharedMemory space.
+/// Assumptions: all page numbers are unique, positive, have an known maximum,
+/// and can be temporary unavailable as long as they are never trully lost.
+class PageStack {
+public:
+ typedef uint32_t Value; ///< stack item type (a free page number)
+
+ /// creates a new shared stack that can hold up to capacity items
+ PageStack(const String &id, const unsigned int capacity);
+ /// attaches to the identified shared stack
+ PageStack(const String &id);
+
+ /// sets value and returns true unless no free page numbers are found
+ bool pop(Value &value);
+ /// makes value available as a free page number to future pop() callers
+ void push(const Value value);
+
+private:
+ typedef unsigned int Offset; ///< stack index type
+
+ struct Shared {
+ Shared(const unsigned int theCapacity);
+
+ // these help iterate the stack in search of a free spot or a page
+ Offset next(const Offset idx) const { return (idx + 1) % theCapacity; }
+ Offset prev(const Offset idx) const { return (theCapacity + idx - 1) % theCapacity; }
+
+ const Offset theCapacity; ///< stack capacity, i.e. theItems size
+ /// lower bound for the number of free pages (may get negative!)
+ AtomicWordT<Offset> theSize;
+
+ /// last readable item index; just a hint, not a guarantee
+ AtomicWordT<Offset> theLastReadable;
+ /// first writable item index; just a hint, not a guarantee
+ AtomicWordT<Offset> theFirstWritable;
+
+ typedef AtomicWordT<Value> Item;
+ Item theItems[]; ///< page number storage
+ };
+
+ SharedMemory shm; ///< shared memory segment to store metadata (and pages)
+ Shared *shared; ///< our metadata, shared among all stack users
+};
+
+} // namespace Mem
+
+} // namespace Ipc
+
+#endif // SQUID_IPC_MEM_PAGE_STACK_H