circular_queue 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: