ichannel 6.0.0 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.pryrc ADDED
@@ -0,0 +1 @@
1
+ require "./lib/ichannel"
data/ChangeLog.txt CHANGED
@@ -1,3 +1,14 @@
1
+ == HEAD
2
+ - Redis#last_msg, UNIXSocket#last_msg changes.
3
+ The last_msg method returns the last value read by #get when a channel is
4
+ not readable.
5
+
6
+ - change IChannel from being a class to a module.
7
+ There's no need to create an instance of IChannel anymore.
8
+
9
+ - Add IChannel::Redis.
10
+ Add Redis as a backend.
11
+
1
12
  == v0.6.0
2
13
  - IChannel::UNIXSocket can now be serialized by Marshal.
3
14
  IChannel::UNIXSocket can be serialized by Marshal but there's a gotcha: it
data/Gemfile CHANGED
@@ -1,7 +1,10 @@
1
1
  source 'http://rubygems.org'
2
2
  group :development do
3
- gem 'rake'
4
- gem 'redcarpet'
5
- gem 'yard'
3
+ gem "pry"
4
+ gem "rake"
5
+ gem "redcarpet"
6
+ gem "yard"
7
+ gem "foreman"
6
8
  end
9
+ gem "redis"
7
10
  gemspec
data/Procfile ADDED
@@ -0,0 +1 @@
1
+ redis: redis-server
data/README.md CHANGED
@@ -1,6 +1,5 @@
1
1
  __OVERVIEW__
2
2
 
3
-
4
3
  | Project | ichannel
5
4
  |:----------------|:--------------------------------------------------
6
5
  | Homepage | https://github.com/robgleeson/ichannel
@@ -11,25 +10,33 @@ __OVERVIEW__
11
10
 
12
11
  __DESCRIPTION__
13
12
 
14
- ichannel simplifies interprocess communication by providing a bi-directional
15
- channel that can be used to transport ruby objects between processes on the same
16
- machine. All communication on a channel occurs on a streamed UNIXSocket that a
17
- channel uses to queues its messages (ruby objects), and also to ensure that
18
- messages are received in the order they are sent.
13
+ ichannel is a channel for interprocess communication between ruby processes on
14
+ the same machine(or network). The basic premise is that you can "put" a ruby
15
+ object onto the channel and on the other end(maybe in a different process,
16
+ or maybe on a different machine) you can "get" the object from the channel.
17
+
18
+ The two main modes of transport are a UNIXSocket(streamed) or [redis](https://redis.io).
19
+ A unix socket is fast and operates without any external dependencies but it
20
+ can't go beyond a single machine. A channel that uses redis can operate between
21
+ different machines. And incase you're wondering ichannel uses a
22
+ redis [list](http://redis.io/commands#list) to queue messages.
23
+
24
+ The last topic I feel I should talk about before the examples is serialization.
25
+ A ruby object is serialized(on write) and deserialized(on read) when passing
26
+ through a channel. A channel can use any serializer that implements the dump and
27
+ load methods but the default is [Marshal](http://ruby-doc.org/core-2.0/Marshal.html).
28
+ There are also a number of objects that cannot be serialized (such as IO,
29
+ anonymous classes/modules, Proc, …) but I've found most of the time I send
30
+ simple objects like Hash.
19
31
 
20
- Underneath the hood ruby objects are serialized when writing and reading from the
21
- underlying UNIXSocket. A ruby object is serialized before a write, and it is
22
- deserialized after a read. The choice of serializer is left up to you, though.
23
- A serializer can be any object that implements `dump` and `load` -- two methods
24
- that are usually implemented by serializers written in ruby.
25
32
 
26
33
  __EXAMPLES__
27
34
 
28
35
  __1.__
29
36
 
30
37
  A demo of how to pass ruby objects through a channel and also between processes.
31
- [Marshal](http://rubydoc.info/stdlib/core/Marshal) is the serializer of choice
32
- in this example:
38
+ [Marshal](http://rubydoc.info/stdlib/core/Marshal) is the serializer of choice,
39
+ and a streamed UNIXSocket is mode of transport:
33
40
 
34
41
  ```ruby
35
42
  channel = IChannel.unix Marshal
@@ -45,7 +52,8 @@ channel.get # => 'Hello!'
45
52
  __2.__
46
53
 
47
54
  Knowing when a channel is readable can be useful so that you can avoid a
48
- blocking read. This (bad) example demonstrates how to do that:
55
+ blocking read on the underlying UNIXSocket. This (bad) example demonstrates
56
+ how to do that:
49
57
 
50
58
  ```ruby
51
59
  channel = IChannel.unix Marshal
@@ -62,6 +70,19 @@ Process.wait pid
62
70
 
63
71
  __3.__
64
72
 
73
+ A demo of how to use ichannel with Redis:
74
+
75
+ ```ruby
76
+ channel = IChannel.redis
77
+ channel.put %w(a)
78
+
79
+ # In another process, far away…
80
+ channel = IChannel.redis
81
+ channel.get # => ["a"]
82
+ ```
83
+
84
+ __4.__
85
+
65
86
  MessagePack doesn't implement `dump` or `load` but a wrapper can be easily
66
87
  written:
67
88
 
@@ -93,6 +114,13 @@ _unsupported_
93
114
 
94
115
  __INSTALL__
95
116
 
117
+ If you plan on using redis you'll need to install the 'redis' gem. It's
118
+ optional:
119
+
120
+ $ gem install redis
121
+
122
+ And to finish:
123
+
96
124
  $ gem install ichannel
97
125
 
98
126
  __SEE ALSO__
@@ -0,0 +1,154 @@
1
+ class IChannel::Redis
2
+
3
+ #
4
+ # @param [#dump,#load] serializer
5
+ # A serializer.
6
+ #
7
+ # @param [Hash] options
8
+ # A Hash of options to pass onto the redis-rb client.
9
+ #
10
+ # @return [IChannel::Redis]
11
+ #
12
+ def initialize(serializer, options)
13
+ @serializer = serializer
14
+ @key = options.delete(:key) || "channel"
15
+ @redis = ::Redis.new(options)
16
+ @last_msg = nil
17
+ @closed = false
18
+ end
19
+
20
+ #
21
+ # @return [Boolean]
22
+ # Returns true when the channel is closed.
23
+ #
24
+ def closed?
25
+ @closed
26
+ end
27
+
28
+ #
29
+ # Close the channel.
30
+ #
31
+ # @return [Boolean]
32
+ # Returns true when the channel has been closed.
33
+ #
34
+ def close
35
+ unless closed?
36
+ @redis.quit
37
+ @last_msg = nil
38
+ @closed = true
39
+ end
40
+ end
41
+
42
+ #
43
+ # Add an object to the channel.
44
+ #
45
+ # @param [Object] object
46
+ # The object to add to the channel.
47
+ #
48
+ # @raise [IOError]
49
+ # When the channel is closed.
50
+ #
51
+ # @return
52
+ # (see Redis#write!)
53
+ #
54
+ def write(object)
55
+ write!(object, nil)
56
+ end
57
+ alias_method :put, :write
58
+
59
+ #
60
+ # Add an object to the channel.
61
+ #
62
+ # @param [Object] object
63
+ # The object to add to the channel.
64
+ #
65
+ # @param [Fixnum] timeout
66
+ # The amount of time to wait for the write to complete.
67
+ #
68
+ # @raise [IOError]
69
+ # When the channel is closed.
70
+ #
71
+ # @raise [Timeout::Error]
72
+ # When the write does not complete in time.
73
+ #
74
+ # @return [void]
75
+ #
76
+ def write!(object, timeout = 0.1)
77
+ if closed?
78
+ raise IOError, 'The channel cannot be written to (closed)'
79
+ end
80
+ Timeout.timeout(timeout) do
81
+ dump = @serializer.dump object
82
+ @redis.lpush @key, dump
83
+ end
84
+ end
85
+ alias_method :put!, :write!
86
+
87
+ #
88
+ # Read an object from the channel.
89
+ #
90
+ # @raise [IOError]
91
+ # When the channel is closed or empty.
92
+ #
93
+ # @return
94
+ # (see Redis#recv!)
95
+ #
96
+ def recv
97
+ recv! nil
98
+ end
99
+ alias_method :get, :recv
100
+
101
+ #
102
+ # @param [Fixnum] timeout
103
+ # The amount of time to wait for the read to complete.
104
+ #
105
+ # @raise [IOError]
106
+ # When the channel is closed or empty.
107
+ #
108
+ # @raise [Timeout::Error]
109
+ # When the read does not complete in time.
110
+ #
111
+ # @return [Object]
112
+ # The object read from the channel.
113
+ #
114
+ def recv!(timeout = 0.1)
115
+ if closed?
116
+ raise IOError, 'The channel cannot be read from (closed).'
117
+ elsif empty?
118
+ raise IOError, 'The channel cannot be read from (empty).'
119
+ else
120
+ Timeout.timeout(timeout) do
121
+ dump = @redis.rpop @key
122
+ @last_msg = @serializer.load dump
123
+ end
124
+ end
125
+ end
126
+ alias_method :get!, :recv!
127
+
128
+ #
129
+ # @return [Object]
130
+ # Returns the last message written to the channel.
131
+ #
132
+ def last_msg
133
+ while readable?
134
+ @last_msg = get
135
+ end
136
+ @last_msg
137
+ end
138
+
139
+ #
140
+ # @return [Boolean]
141
+ # Returns true when the channel is empty.
142
+ #
143
+ def empty?
144
+ @redis.llen(@key) == 0
145
+ end
146
+
147
+ #
148
+ # @return [Boolean]
149
+ # Returns true when the channel is readable.
150
+ #
151
+ def readable?
152
+ !closed? && !empty?
153
+ end
154
+ end
@@ -1,5 +1,5 @@
1
1
  require 'socket'
2
- class IChannel
2
+ module IChannel
3
3
  class UNIXSocket
4
4
  SEP = '_$_'
5
5
  if respond_to? :private_constant
@@ -10,6 +10,8 @@ class IChannel
10
10
  # @param [#dump,#load] serializer
11
11
  # Any object that implements dump, & load.
12
12
  #
13
+ # @return [IChannel::UNIXSocket]
14
+ #
13
15
  def initialize(serializer = Marshal, adapter_options)
14
16
  @serializer = serializer
15
17
  @last_msg = nil
@@ -139,7 +141,7 @@ class IChannel
139
141
  readable, _ = IO.select [@reader], nil, nil, timeout
140
142
  if readable
141
143
  msg = readable[0].readline(SEP).chomp SEP
142
- @serializer.load msg
144
+ @last_msg = @serializer.load msg
143
145
  else
144
146
  raise IOError, 'The channel cannot be read from.'
145
147
  end
@@ -1,3 +1,3 @@
1
- class IChannel
2
- VERSION = "6.0.0"
1
+ module IChannel
2
+ VERSION = "6.1.0"
3
3
  end
data/lib/ichannel.rb CHANGED
@@ -1,11 +1,30 @@
1
- class IChannel
1
+ module IChannel
2
+ require 'redis'
2
3
  require_relative "ichannel/unix_socket"
4
+ require_relative "ichannel/redis"
3
5
 
4
- def self.unix(options)
5
- UNIXSocket.new(options)
6
+ #
7
+ # @param
8
+ # (see UNIXSocket#initialize)
9
+ #
10
+ # @return
11
+ # (see UNIXSocket#initialize)
12
+ #
13
+ def self.unix(serializer = Marshal, options = {})
14
+ UNIXSocket.new serializer, options
6
15
  end
7
16
 
8
- def self.redis(options)
9
- raise NotImplementedError
17
+ #
18
+ # @param
19
+ # (see Redis#initialize)
20
+ #
21
+ # @return
22
+ # (see Redis#initialize)
23
+ #
24
+ def self.redis(serializer = Marshal, options = {})
25
+ unless defined?(::Redis)
26
+ require 'redis'
27
+ end
28
+ Redis.new serializer, options
10
29
  end
11
30
  end
@@ -0,0 +1,17 @@
1
+ require_relative 'setup'
2
+ require_relative 'ichannel_unix_test'
3
+ class IChannelRedisTest < IChannelUNIXTest
4
+ def setup
5
+ serializer = Object.const_get ENV["SERIALIZER"] || "Marshal"
6
+ @channel = IChannel.redis serializer
7
+ end
8
+
9
+ def teardown
10
+ key = @channel.instance_variable_get :@key
11
+ @channel.instance_variable_get(:@redis).del(key)
12
+ @channel.close
13
+ end
14
+
15
+ def test_serialization() end
16
+ def test_serialization_in_fork() end
17
+ end
@@ -1,5 +1,5 @@
1
1
  require_relative 'setup'
2
- class IChannelTest < Test::Unit::TestCase
2
+ class IChannelUNIXTest < Test::Unit::TestCase
3
3
  def setup
4
4
  serializer = Object.const_get ENV["SERIALIZER"] || "Marshal"
5
5
  @channel = IChannel.unix serializer
@@ -26,6 +26,21 @@ class IChannelTest < Test::Unit::TestCase
26
26
  end
27
27
  end
28
28
 
29
+ def test_last_msg_after_read
30
+ @channel.put 42
31
+ @channel.get
32
+ assert_equal 42, @channel.last_msg
33
+ end
34
+
35
+ def test_serialization_in_fork
36
+ dump = Marshal.dump(@channel)
37
+ pid = fork do
38
+ Marshal.load(dump).put 42
39
+ end
40
+ Process.wait pid
41
+ assert_equal 42, @channel.get
42
+ end
43
+
29
44
  def test_serialization
30
45
  @channel.put 42
31
46
  dump = Marshal.dump @channel
data/test/setup.rb CHANGED
@@ -1,5 +1,6 @@
1
+ require "bundler"
2
+ Bundler.require :default
1
3
  require_relative '../lib/ichannel'
2
-
3
4
  require 'yaml'
4
5
  require 'json'
5
6
  require 'test/unit'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ichannel
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.0.0
4
+ version: 6.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-20 00:00:00.000000000 Z
12
+ date: 2013-05-03 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: ! "A modern and easy-to-use interprocess communication \n primitive."
15
15
  email:
@@ -20,18 +20,22 @@ extra_rdoc_files: []
20
20
  files:
21
21
  - .gitattributes
22
22
  - .gitignore
23
+ - .pryrc
23
24
  - .travis.yml
24
25
  - .yardopts
25
26
  - ChangeLog.txt
26
27
  - Gemfile
27
28
  - LICENSE.txt
29
+ - Procfile
28
30
  - README.md
29
31
  - Rakefile
30
32
  - ichannel.gemspec
31
33
  - lib/ichannel.rb
34
+ - lib/ichannel/redis.rb
32
35
  - lib/ichannel/unix_socket.rb
33
36
  - lib/ichannel/version.rb
34
- - test/ichannel_class_test.rb
37
+ - test/ichannel_redis_test.rb
38
+ - test/ichannel_unix_test.rb
35
39
  - test/setup.rb
36
40
  homepage: https://github.com/robgleeson/ichannel
37
41
  licenses: []
@@ -47,7 +51,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
47
51
  version: '0'
48
52
  segments:
49
53
  - 0
50
- hash: -4148613930352415051
54
+ hash: 3267569127984942618
51
55
  required_rubygems_version: !ruby/object:Gem::Requirement
52
56
  none: false
53
57
  requirements:
@@ -56,7 +60,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
56
60
  version: '0'
57
61
  segments:
58
62
  - 0
59
- hash: -4148613930352415051
63
+ hash: 3267569127984942618
60
64
  requirements: []
61
65
  rubyforge_project:
62
66
  rubygems_version: 1.8.23
@@ -64,6 +68,7 @@ signing_key:
64
68
  specification_version: 3
65
69
  summary: A modern and easy-to-use interprocess communication primitive.
66
70
  test_files:
67
- - test/ichannel_class_test.rb
71
+ - test/ichannel_redis_test.rb
72
+ - test/ichannel_unix_test.rb
68
73
  - test/setup.rb
69
74
  has_rdoc: