ruby_channel 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 +3 -0
- data/Gemfile +4 -0
- data/README +7 -0
- data/Rakefile +2 -0
- data/lib/ruby_channel.rb +29 -0
- data/lib/ruby_channel/channel.rb +140 -0
- data/lib/ruby_channel/selector.rb +63 -0
- data/lib/ruby_channel/version.rb +3 -0
- data/ruby_channel.gemspec +22 -0
- metadata +89 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README
ADDED
data/Rakefile
ADDED
data/lib/ruby_channel.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'monitor'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
require 'ruby_channel/channel.rb'
|
5
|
+
require 'ruby_channel/selector.rb'
|
6
|
+
require 'ruby_channel/version.rb'
|
7
|
+
|
8
|
+
module RubyChannel
|
9
|
+
CHANNEL_DEBUG = false
|
10
|
+
end
|
11
|
+
|
12
|
+
module Kernel
|
13
|
+
def go(*args)
|
14
|
+
Thread.new do
|
15
|
+
begin
|
16
|
+
yield(*args)
|
17
|
+
rescue Exception => e
|
18
|
+
p e
|
19
|
+
p e.backtrace
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def select_channel
|
25
|
+
s = RubyChannel::Selector.new
|
26
|
+
yield s
|
27
|
+
s.select
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
module RubyChannel
|
2
|
+
class Channel
|
3
|
+
attr_reader :mutex, :queue
|
4
|
+
|
5
|
+
#
|
6
|
+
# Creates a new channel.
|
7
|
+
#
|
8
|
+
def initialize
|
9
|
+
@queue = []
|
10
|
+
@waiting = []
|
11
|
+
@mutex = Mutex.new
|
12
|
+
end
|
13
|
+
|
14
|
+
#
|
15
|
+
# Pushes +obj+ to the queue.
|
16
|
+
#
|
17
|
+
def push(obj)
|
18
|
+
@mutex.synchronize do
|
19
|
+
@queue.push obj
|
20
|
+
begin
|
21
|
+
t = @waiting.shift
|
22
|
+
t.wakeup if t
|
23
|
+
rescue ThreadError
|
24
|
+
retry
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# Alias of push
|
31
|
+
#
|
32
|
+
alias << push
|
33
|
+
|
34
|
+
#
|
35
|
+
# Retrieves data from channel. If the channel is empty, the calling thread is
|
36
|
+
# suspended until data is pushed onto channel.
|
37
|
+
#
|
38
|
+
def pop
|
39
|
+
@mutex.synchronize do
|
40
|
+
loop do
|
41
|
+
if @queue.empty?
|
42
|
+
@waiting.push Thread.current
|
43
|
+
@mutex.sleep
|
44
|
+
else
|
45
|
+
return @queue.shift
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Retrieves data from channel like method +pop+, but if +non_block+ is true, the
|
53
|
+
# thread isn't suspended, and an exception is raised.
|
54
|
+
#
|
55
|
+
def pop!
|
56
|
+
@mutex.synchronize do
|
57
|
+
loop do
|
58
|
+
if @queue.empty?
|
59
|
+
raise ThreadError, "Empty Channel"
|
60
|
+
@waiting.push Thread.current
|
61
|
+
@mutex.sleep
|
62
|
+
else
|
63
|
+
return @queue.shift
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
#
|
70
|
+
# Redirect signal to other channel
|
71
|
+
#
|
72
|
+
def redirect_to(channel, callback_method=nil, *args)
|
73
|
+
value = self.pop
|
74
|
+
value.send(callback_method, *args) if callback_method
|
75
|
+
yield value if block_given?
|
76
|
+
channel << value
|
77
|
+
end
|
78
|
+
alias >> redirect_to
|
79
|
+
|
80
|
+
#
|
81
|
+
# Returns +true+ if the channel is empty.
|
82
|
+
#
|
83
|
+
def empty?
|
84
|
+
@queue.empty?
|
85
|
+
end
|
86
|
+
|
87
|
+
#
|
88
|
+
# Removes all objects from the channel.
|
89
|
+
#
|
90
|
+
def clear
|
91
|
+
@queue.clear
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# Returns the length of the channel.
|
96
|
+
#
|
97
|
+
def length
|
98
|
+
@queue.length
|
99
|
+
end
|
100
|
+
|
101
|
+
#
|
102
|
+
# Alias of length.
|
103
|
+
#
|
104
|
+
alias size length
|
105
|
+
|
106
|
+
#
|
107
|
+
# Returns the number of threads waiting on the queue.
|
108
|
+
#
|
109
|
+
def waiting_size
|
110
|
+
@waiting.size
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
# Method called only by selector to subscribe listeners, dont
|
115
|
+
# use it, unless you understand exactly what you're doing!
|
116
|
+
#
|
117
|
+
def subscribe(selector)
|
118
|
+
channel = self
|
119
|
+
@mutex.synchronize do
|
120
|
+
loop do
|
121
|
+
return selector.result unless selector.waiting?
|
122
|
+
if @queue.empty?
|
123
|
+
@waiting.push Thread.current
|
124
|
+
@mutex.sleep
|
125
|
+
else
|
126
|
+
selector.mutex.synchronize do
|
127
|
+
if selector.waiting?
|
128
|
+
result = selector.update_result(channel, @queue.shift)
|
129
|
+
yield result
|
130
|
+
end
|
131
|
+
end
|
132
|
+
selector.release_result
|
133
|
+
return selector.result
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module RubyChannel
|
2
|
+
class Selector
|
3
|
+
attr_reader :waiting, :result, :mutex
|
4
|
+
alias waiting? waiting
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@threads = []
|
8
|
+
@waiting = true
|
9
|
+
@mutex = Mutex.new
|
10
|
+
@return_thread = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def listen(channel, &block)
|
14
|
+
selector = self
|
15
|
+
@threads << Thread.new do
|
16
|
+
Thread.current[:name] = "Listener" if RubyChannel::CHANNEL_DEBUG
|
17
|
+
channel.subscribe(selector, &block)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def timeout(value, &block)
|
22
|
+
timeout_channel = Channel.new
|
23
|
+
Thread.new do
|
24
|
+
Thread.current[:name] = "Timeout Listener" if RubyChannel::CHANNEL_DEBUG
|
25
|
+
sleep value
|
26
|
+
timeout_channel << :timeout
|
27
|
+
end
|
28
|
+
listen timeout_channel, &block
|
29
|
+
end
|
30
|
+
|
31
|
+
def default(&block)
|
32
|
+
default_channel = Channel.new
|
33
|
+
Thread.new do
|
34
|
+
Thread.current[:name] = "Default Listener" if RubyChannel::CHANNEL_DEBUG
|
35
|
+
default_channel << :default
|
36
|
+
end
|
37
|
+
listen default_channel, &block
|
38
|
+
end
|
39
|
+
|
40
|
+
def select
|
41
|
+
Thread.current[:name] = "select" if RubyChannel::CHANNEL_DEBUG
|
42
|
+
@mutex.synchronize do
|
43
|
+
if waiting?
|
44
|
+
@return_thread = Thread.current
|
45
|
+
Thread.list.each{|t| puts "#{t.inspect}: #{t[:name]}"} if RubyChannel::CHANNEL_DEBUG
|
46
|
+
@mutex.sleep
|
47
|
+
end
|
48
|
+
end
|
49
|
+
@threads.each{ |thread| thread.wakeup }
|
50
|
+
@threads.clear
|
51
|
+
return @result
|
52
|
+
end
|
53
|
+
|
54
|
+
def update_result(channel, value)
|
55
|
+
@waiting = false
|
56
|
+
@result = value
|
57
|
+
end
|
58
|
+
|
59
|
+
def release_result
|
60
|
+
@return_thread.run if @return_thread
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path("../lib/ruby_channel/version", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "ruby_channel"
|
6
|
+
s.version = RubyChannel::VERSION
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.authors = ['Dalton Pinto']
|
9
|
+
s.email = ['dalthon@aluno.ita.br']
|
10
|
+
s.homepage = "http://rubygems.org/gems/ruby_channel"
|
11
|
+
s.summary = "A way to use channels in Ruby, inspired by Google's Go"
|
12
|
+
s.description = "A way to use channels in Ruby, inspired by Google's Go and Ilya Grigorik's gem 'agent'"
|
13
|
+
|
14
|
+
s.required_rubygems_version = ">= 1.3.6"
|
15
|
+
s.rubyforge_project = "ruby_channel"
|
16
|
+
|
17
|
+
s.add_development_dependency "bundler", ">= 1.0.0"
|
18
|
+
|
19
|
+
s.files = `git ls-files`.split("\n")
|
20
|
+
s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
|
21
|
+
s.require_path = 'lib'
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby_channel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Dalton Pinto
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-02-09 00:00:00 -02:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: bundler
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 1
|
30
|
+
- 0
|
31
|
+
- 0
|
32
|
+
version: 1.0.0
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
description: A way to use channels in Ruby, inspired by Google's Go and Ilya Grigorik's gem 'agent'
|
36
|
+
email:
|
37
|
+
- dalthon@aluno.ita.br
|
38
|
+
executables: []
|
39
|
+
|
40
|
+
extensions: []
|
41
|
+
|
42
|
+
extra_rdoc_files: []
|
43
|
+
|
44
|
+
files:
|
45
|
+
- .gitignore
|
46
|
+
- Gemfile
|
47
|
+
- README
|
48
|
+
- Rakefile
|
49
|
+
- lib/ruby_channel.rb
|
50
|
+
- lib/ruby_channel/channel.rb
|
51
|
+
- lib/ruby_channel/selector.rb
|
52
|
+
- lib/ruby_channel/version.rb
|
53
|
+
- ruby_channel.gemspec
|
54
|
+
has_rdoc: true
|
55
|
+
homepage: http://rubygems.org/gems/ruby_channel
|
56
|
+
licenses: []
|
57
|
+
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
segments:
|
69
|
+
- 0
|
70
|
+
version: "0"
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
segments:
|
77
|
+
- 1
|
78
|
+
- 3
|
79
|
+
- 6
|
80
|
+
version: 1.3.6
|
81
|
+
requirements: []
|
82
|
+
|
83
|
+
rubyforge_project: ruby_channel
|
84
|
+
rubygems_version: 1.3.7
|
85
|
+
signing_key:
|
86
|
+
specification_version: 3
|
87
|
+
summary: A way to use channels in Ruby, inspired by Google's Go
|
88
|
+
test_files: []
|
89
|
+
|