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 ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ doc
6
+ .yardoc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in circular_queue.gemspec
4
+ gemspec
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
@@ -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: