circular_queue 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +6 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/circular_queue.gemspec +23 -0
- data/lib/circular_queue.rb +149 -0
- data/spec/circular_queue_spec.rb +97 -0
- data/spec/spec_helper.rb +1 -0
- metadata +96 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "circular_queue"
|
6
|
+
s.version = "0.0.1"
|
7
|
+
s.authors = ["Andy Lindeman"]
|
8
|
+
s.email = ["alindeman@gmail.com"]
|
9
|
+
s.homepage = ""
|
10
|
+
s.summary = %q{Data structure that uses a single, fixed-size buffer as if it were connected end-to-end}
|
11
|
+
s.description = %q{A circular queue (also called a circular buffer or ring buffer) is useful when buffering data streams}
|
12
|
+
|
13
|
+
s.rubyforge_project = "circular_queue"
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_development_dependency "rspec", "~>2.7.0"
|
21
|
+
s.add_development_dependency "rake", "~>0.9.2"
|
22
|
+
s.add_development_dependency "yard"
|
23
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require "thread"
|
2
|
+
|
3
|
+
# A thread-safe queue with a size limitation. When more elements than the
|
4
|
+
# capacity are added, the queue either loops back on itself (removing the
|
5
|
+
# oldest elements first) or raises an error (if `enq!` is used).
|
6
|
+
#
|
7
|
+
# Useful for streaming data where keeping up with real-time is more important
|
8
|
+
# than consuming every message if load rises and the queue backs up.
|
9
|
+
#
|
10
|
+
# Exposes the same interface as the `Queue` from the Ruby stdlib.
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
#
|
14
|
+
# # Capacity of 3
|
15
|
+
# q = CircularQueue.new(3)
|
16
|
+
#
|
17
|
+
# q << 1 # => [1]
|
18
|
+
# q << 2 # => [1, 2]
|
19
|
+
# q << 3 # => [1, 2, 3]
|
20
|
+
#
|
21
|
+
# # Elements are replaced when the queue reaches capacity
|
22
|
+
# q << 4 # => [4, 2, 3]
|
23
|
+
# q << 5 # => [4, 5, 3]
|
24
|
+
class CircularQueue
|
25
|
+
# Returns the maximum number of elements that can be enqueued
|
26
|
+
# @return [Integer]
|
27
|
+
attr_reader :capacity
|
28
|
+
|
29
|
+
# Returns the number of elements in the queue
|
30
|
+
# @return [Integer]
|
31
|
+
attr_reader :size
|
32
|
+
alias length size
|
33
|
+
|
34
|
+
# Creates a new queue of the specified capacity
|
35
|
+
# @param [Integer] the maximum capacity of the queue
|
36
|
+
def initialize(capacity)
|
37
|
+
@capacity = capacity
|
38
|
+
@data = Array.new(capacity)
|
39
|
+
|
40
|
+
@mutex = Mutex.new
|
41
|
+
@waiting = Array.new
|
42
|
+
|
43
|
+
clear
|
44
|
+
end
|
45
|
+
|
46
|
+
# Adds an item to the queue
|
47
|
+
# @param [Object] item to add
|
48
|
+
def enq(item)
|
49
|
+
@mutex.synchronize do
|
50
|
+
enq_item(item)
|
51
|
+
wakeup_next_waiter
|
52
|
+
end
|
53
|
+
end
|
54
|
+
alias << enq
|
55
|
+
alias push enq
|
56
|
+
|
57
|
+
# Adds an item to the queue, raising an error if the queue is full
|
58
|
+
# @param [Object] item to add
|
59
|
+
# @raise [ThreadError] queue is full
|
60
|
+
def enq!(item)
|
61
|
+
@mutex.synchronize do
|
62
|
+
raise ThreadError.new("Queue is full") if full?
|
63
|
+
|
64
|
+
enq_item(item)
|
65
|
+
wakeup_next_waiter
|
66
|
+
end
|
67
|
+
end
|
68
|
+
alias push! enq!
|
69
|
+
|
70
|
+
# Removes an item from the queue
|
71
|
+
# @param [Boolean] true to raise an error if the queue is empty; otherwise,
|
72
|
+
# waits for an item to arrive from another thread
|
73
|
+
# @raise [ThreadError] non_block was true and the queue was empty
|
74
|
+
def deq(non_block = false)
|
75
|
+
@mutex.synchronize do
|
76
|
+
while true
|
77
|
+
if empty?
|
78
|
+
raise ThreadError.new("Queue is empty") if non_block
|
79
|
+
|
80
|
+
@waiting.push(Thread.current) unless @waiting.include?(Thread.current)
|
81
|
+
@mutex.sleep
|
82
|
+
else
|
83
|
+
return deq_item
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
alias shift deq
|
89
|
+
alias pop deq
|
90
|
+
|
91
|
+
# Removes all items from the queue
|
92
|
+
def clear
|
93
|
+
@mutex.synchronize do
|
94
|
+
@size = 0
|
95
|
+
@front = 0
|
96
|
+
@back = 0
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns whether the queue is empty
|
101
|
+
# @return [Boolean] queue is empty
|
102
|
+
def empty?
|
103
|
+
@size == 0
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns whether the queue is full
|
107
|
+
# @return [Boolean] queue is full
|
108
|
+
def full?
|
109
|
+
@size == @capacity
|
110
|
+
end
|
111
|
+
|
112
|
+
# Returns the number of threads waiting for items to arrive in the queue
|
113
|
+
# @return [Integer] number of threads waiting
|
114
|
+
def num_waiting
|
115
|
+
@waiting.length
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def enq_item(item)
|
121
|
+
@data[@back] = item
|
122
|
+
|
123
|
+
@size += 1 unless full?
|
124
|
+
|
125
|
+
@back += 1
|
126
|
+
@back %= @capacity
|
127
|
+
end
|
128
|
+
|
129
|
+
def deq_item
|
130
|
+
item = @data[@front]
|
131
|
+
|
132
|
+
@size -= 1
|
133
|
+
|
134
|
+
@front += 1
|
135
|
+
@front %= @capacity
|
136
|
+
|
137
|
+
item
|
138
|
+
end
|
139
|
+
|
140
|
+
def wakeup_next_waiter
|
141
|
+
begin
|
142
|
+
if thread = @waiting.shift
|
143
|
+
thread.wakeup
|
144
|
+
end
|
145
|
+
rescue ThreadError
|
146
|
+
retry
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe CircularQueue do
|
4
|
+
let(:capacity) { 5 }
|
5
|
+
subject { described_class.new(capacity) }
|
6
|
+
|
7
|
+
describe "initialization" do
|
8
|
+
it "can be initialized with a capacity" do
|
9
|
+
subject.capacity.should == capacity
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "adding items" do
|
14
|
+
it "accepts new items" do
|
15
|
+
subject.enq(1234)
|
16
|
+
subject.deq.should == 1234
|
17
|
+
end
|
18
|
+
|
19
|
+
it "increases its size when a new item is added" do
|
20
|
+
subject.enq(1234)
|
21
|
+
subject.size.should == 1
|
22
|
+
end
|
23
|
+
|
24
|
+
it "presents the appearance of accepting infinite items" do
|
25
|
+
1.upto(capacity * 2) { |i| subject.enq(i) }
|
26
|
+
|
27
|
+
1.upto(capacity) do |i|
|
28
|
+
subject.deq.should == i + capacity
|
29
|
+
end
|
30
|
+
|
31
|
+
subject.size.should be_zero
|
32
|
+
end
|
33
|
+
|
34
|
+
it "raises an error if the queue is full and enq! is used to add items" do
|
35
|
+
1.upto(capacity) { |i| subject.enq(i) }
|
36
|
+
|
37
|
+
expect {
|
38
|
+
subject.enq!(1)
|
39
|
+
}.to raise_error(ThreadError)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "removing items" do
|
44
|
+
it "removes items from the queue until the queue is empty" do
|
45
|
+
1.upto(capacity) { |i| subject.enq(i) }
|
46
|
+
|
47
|
+
1.upto(capacity) do |i|
|
48
|
+
subject.deq.should == i
|
49
|
+
end
|
50
|
+
|
51
|
+
subject.size.should be_zero
|
52
|
+
end
|
53
|
+
|
54
|
+
context "when empty" do
|
55
|
+
context "non-blocking" do
|
56
|
+
it "raises a ThreadError if the queue is empty" do
|
57
|
+
subject.clear
|
58
|
+
|
59
|
+
expect {
|
60
|
+
subject.deq(true)
|
61
|
+
}.to raise_error(ThreadError)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "blocking" do
|
66
|
+
it "waits for another thread to add an item" do
|
67
|
+
# Another queue that is doing work is required or a deadlock will
|
68
|
+
# be detected
|
69
|
+
enqueue = false
|
70
|
+
done = false
|
71
|
+
|
72
|
+
enqueue_thread = Thread.new do
|
73
|
+
until done
|
74
|
+
subject.enq(1) if enqueue
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
begin
|
79
|
+
enqueue = true
|
80
|
+
subject.deq.should == 1
|
81
|
+
ensure
|
82
|
+
done = true
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "clearing the queue" do
|
90
|
+
it "removes all items from the queue" do
|
91
|
+
subject.enq(1)
|
92
|
+
subject.clear
|
93
|
+
|
94
|
+
subject.size.should be_zero
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.expand_path("../lib/circular_queue", File.dirname(__FILE__))
|
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: circular_queue
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Andy Lindeman
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-10-17 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &2156106760 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.7.0
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2156106760
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rake
|
27
|
+
requirement: &2156106060 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.9.2
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2156106060
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: yard
|
38
|
+
requirement: &2156105200 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *2156105200
|
47
|
+
description: A circular queue (also called a circular buffer or ring buffer) is useful
|
48
|
+
when buffering data streams
|
49
|
+
email:
|
50
|
+
- alindeman@gmail.com
|
51
|
+
executables: []
|
52
|
+
extensions: []
|
53
|
+
extra_rdoc_files: []
|
54
|
+
files:
|
55
|
+
- .gitignore
|
56
|
+
- Gemfile
|
57
|
+
- Rakefile
|
58
|
+
- circular_queue.gemspec
|
59
|
+
- lib/circular_queue.rb
|
60
|
+
- spec/circular_queue_spec.rb
|
61
|
+
- spec/spec_helper.rb
|
62
|
+
homepage: ''
|
63
|
+
licenses: []
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options: []
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
hash: -2375285051311577887
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
segments:
|
84
|
+
- 0
|
85
|
+
hash: -2375285051311577887
|
86
|
+
requirements: []
|
87
|
+
rubyforge_project: circular_queue
|
88
|
+
rubygems_version: 1.8.10
|
89
|
+
signing_key:
|
90
|
+
specification_version: 3
|
91
|
+
summary: Data structure that uses a single, fixed-size buffer as if it were connected
|
92
|
+
end-to-end
|
93
|
+
test_files:
|
94
|
+
- spec/circular_queue_spec.rb
|
95
|
+
- spec/spec_helper.rb
|
96
|
+
has_rdoc:
|