Class: Philiprehberger::QueueStack::Stack

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/philiprehberger/queue_stack/stack.rb

Overview

Thread-safe LIFO stack with optional capacity limit and blocking operations.

Examples:

s = Stack.new(capacity: 10)
s.push('item')
s.pop  # => 'item'

Instance Method Summary collapse

Constructor Details

#initialize(capacity: nil) ⇒ Stack

Create a new stack.

Parameters:

  • capacity (Integer, nil) (defaults to: nil)

    maximum number of items (nil for unlimited)



17
18
19
20
21
22
23
24
# File 'lib/philiprehberger/queue_stack/stack.rb', line 17

def initialize(capacity: nil)
  @items = []
  @capacity = capacity
  @closed = false
  @mutex = Mutex.new
  @not_empty = ConditionVariable.new
  @not_full = ConditionVariable.new
end

Instance Method Details

#clearvoid

This method returns an undefined value.

Remove all items without returning them. Signals any blocked producers.



92
93
94
95
96
97
# File 'lib/philiprehberger/queue_stack/stack.rb', line 92

def clear
  @mutex.synchronize do
    @items.clear
    @not_full.broadcast
  end
end

#closevoid

This method returns an undefined value.

Mark the stack as closed. New push calls will raise ClosedError. Existing items can still be popped. Wakes all waiting threads.



156
157
158
159
160
161
162
# File 'lib/philiprehberger/queue_stack/stack.rb', line 156

def close
  @mutex.synchronize do
    @closed = true
    @not_empty.broadcast
    @not_full.broadcast
  end
end

#closed?Boolean

Whether the stack has been closed.

Returns:

  • (Boolean)


167
168
169
# File 'lib/philiprehberger/queue_stack/stack.rb', line 167

def closed?
  @mutex.synchronize { @closed }
end

#drainArray

Remove and return all items as an array (LIFO order, top first). Non-blocking.

Returns:

  • (Array)

    all items in LIFO order (top first)



123
124
125
126
127
128
129
130
# File 'lib/philiprehberger/queue_stack/stack.rb', line 123

def drain
  @mutex.synchronize do
    result = @items.reverse
    @items.clear
    @not_full.broadcast
    result
  end
end

#each {|item| ... } ⇒ Enumerator, self

Iterate items without removing them (snapshot of current state, LIFO order). Returns an Enumerator if no block is given.

Yields:

  • (item)

    each item in LIFO order (top first)

Returns:

  • (Enumerator, self)


137
138
139
140
141
142
143
# File 'lib/philiprehberger/queue_stack/stack.rb', line 137

def each(&block)
  snapshot = @mutex.synchronize { @items.reverse }
  return snapshot.each unless block

  snapshot.each(&block)
  self
end

#empty?Boolean

Whether the stack is empty.

Returns:

  • (Boolean)


188
189
190
# File 'lib/philiprehberger/queue_stack/stack.rb', line 188

def empty?
  @mutex.synchronize { @items.empty? }
end

#full?Boolean

Whether the stack is at capacity.

Returns:

  • (Boolean)


195
196
197
# File 'lib/philiprehberger/queue_stack/stack.rb', line 195

def full?
  @mutex.synchronize { @capacity ? @items.length >= @capacity : false }
end

#peekObject?

Peek at the top item without removing it.

Returns:

  • (Object, nil)

    the top item or nil if empty



174
175
176
# File 'lib/philiprehberger/queue_stack/stack.rb', line 174

def peek
  @mutex.synchronize { @items.last }
end

#popObject?

Pop and return the top item. Blocks if empty (returns nil if closed and empty).

Returns:

  • (Object, nil)

    the popped item or nil if closed and empty



44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/philiprehberger/queue_stack/stack.rb', line 44

def pop
  @mutex.synchronize do
    while @items.empty?
      return nil if @closed

      @not_empty.wait(@mutex)
    end
    item = @items.pop
    @not_full.signal
    item
  end
end

#push(item) ⇒ void

This method returns an undefined value.

Push an item onto the top of the stack. Blocks if at capacity.

Parameters:

  • item (Object)

    the item to push

Raises:



31
32
33
34
35
36
37
38
39
# File 'lib/philiprehberger/queue_stack/stack.rb', line 31

def push(item)
  @mutex.synchronize do
    raise ClosedError, 'cannot push on a closed stack' if @closed

    @not_full.wait(@mutex) while @capacity && @items.length >= @capacity
    @items.push(item)
    @not_empty.signal
  end
end

#sizeInteger

Return the number of items in the stack.

Returns:

  • (Integer)


181
182
183
# File 'lib/philiprehberger/queue_stack/stack.rb', line 181

def size
  @mutex.synchronize { @items.length }
end

#to_aArray

Return a snapshot of items as an array (LIFO order, top first).

Returns:

  • (Array)


148
149
150
# File 'lib/philiprehberger/queue_stack/stack.rb', line 148

def to_a
  @mutex.synchronize { @items.reverse }
end

#try_pop(timeout:) ⇒ Object?

Try to pop an item with a timeout.

Parameters:

  • timeout (Numeric)

    seconds to wait

Returns:

  • (Object, nil)

    the popped item or nil on timeout



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/philiprehberger/queue_stack/stack.rb', line 103

def try_pop(timeout:)
  deadline = Time.now + timeout
  @mutex.synchronize do
    while @items.empty?
      return nil if @closed

      remaining = deadline - Time.now
      return nil if remaining <= 0

      @not_empty.wait(@mutex, remaining)
    end
    item = @items.pop
    @not_full.signal
    item
  end
end

#try_push(item, timeout: nil) ⇒ Boolean

Try to push an item without blocking indefinitely.

With timeout: nil, returns immediately. With a numeric timeout, waits up to that many seconds for space to become available.

Parameters:

  • item (Object)

    the item to push

  • timeout (Numeric, nil) (defaults to: nil)

    seconds to wait, or nil for non-blocking

Returns:

  • (Boolean)

    true if pushed, false if full (or timed out)

Raises:



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/philiprehberger/queue_stack/stack.rb', line 66

def try_push(item, timeout: nil)
  @mutex.synchronize do
    raise ClosedError, 'cannot push on a closed stack' if @closed

    if @capacity && @items.length >= @capacity
      return false if timeout.nil? || timeout <= 0

      deadline = Time.now + timeout
      while @items.length >= @capacity
        remaining = deadline - Time.now
        return false if remaining <= 0

        @not_full.wait(@mutex, remaining)
        raise ClosedError, 'cannot push on a closed stack' if @closed
      end
    end

    @items.push(item)
    @not_empty.signal
    true
  end
end