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.



174
175
176
177
178
179
180
# File 'lib/philiprehberger/queue_stack/stack.rb', line 174

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)


185
186
187
# File 'lib/philiprehberger/queue_stack/stack.rb', line 185

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)



141
142
143
144
145
146
147
148
# File 'lib/philiprehberger/queue_stack/stack.rb', line 141

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)


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

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)


206
207
208
# File 'lib/philiprehberger/queue_stack/stack.rb', line 206

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

#full?Boolean

Whether the stack is at capacity.

Returns:

  • (Boolean)


213
214
215
# File 'lib/philiprehberger/queue_stack/stack.rb', line 213

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



192
193
194
# File 'lib/philiprehberger/queue_stack/stack.rb', line 192

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

#pop_if {|item| ... } ⇒ Object?

Conditionally pop the top item. The block is called with the item that would be popped next. If the block returns truthy, the item is removed and returned. Otherwise the item is left in place and nil is returned. Returns nil immediately if the stack is empty (non-blocking).

Yields:

  • (item)

    the top item

Returns:

  • (Object, nil)

    the removed item, or nil if empty or block returned false



106
107
108
109
110
111
112
113
114
115
# File 'lib/philiprehberger/queue_stack/stack.rb', line 106

def pop_if
  @mutex.synchronize do
    return nil if @items.empty?
    return nil unless yield(@items.last)

    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)


199
200
201
# File 'lib/philiprehberger/queue_stack/stack.rb', line 199

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

#to_aArray

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

Returns:

  • (Array)


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

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



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/philiprehberger/queue_stack/stack.rb', line 121

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