ichannel 6.0.0 → 6.1.0

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/.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: