noeq 0.1.0 → 0.2.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/README.md +13 -0
- data/TODO +2 -0
- data/lib/noeq.rb +73 -8
- data/noeq.gemspec +1 -1
- data/test/noeq_test.rb +89 -0
- metadata +6 -3
data/README.md
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
noeq-rb is a [noeqd](https://github.com/bmizerany/noeqd) GUID client in Ruby.
|
4
4
|
|
5
|
+
[Annotated source code is available](http://titanous.com/noeq-rb/).
|
6
|
+
|
5
7
|
## Installation
|
6
8
|
|
7
9
|
```
|
@@ -26,3 +28,14 @@ noeq = Noeq.new('idserver.local')
|
|
26
28
|
noeq.generate #=> 142692638036852736
|
27
29
|
noeq.generate(5) #=> [142692782450933760, 142692782450933761, 142692782450933762, 142692782450933763, 142692782450933764]
|
28
30
|
```
|
31
|
+
|
32
|
+
### Async usage
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
require 'noeq'
|
36
|
+
|
37
|
+
noeq = Noeq.new('localhost', 4444, :async => true)
|
38
|
+
noeq.request_id
|
39
|
+
# do some things
|
40
|
+
noeq.fetch_id #=> 142692638036852736
|
41
|
+
```
|
data/TODO
ADDED
data/lib/noeq.rb
CHANGED
@@ -1,6 +1,13 @@
|
|
1
|
+
# **Noeq** generates GUIDs using [noeqd](https://github.com/bmizerany/noeqd).
|
2
|
+
|
3
|
+
# `noeqd` uses a simple TCP wire protocol, so let's require our only dependency,
|
4
|
+
# `socket`.
|
1
5
|
require 'socket'
|
2
6
|
|
3
7
|
class Noeq
|
8
|
+
|
9
|
+
# If you just want to test out `noeq` or need to use it in a one-off script,
|
10
|
+
# this method allows for very simple usage.
|
4
11
|
def self.generate(n=1)
|
5
12
|
noeq = new
|
6
13
|
ids = noeq.generate(n)
|
@@ -8,36 +15,94 @@ class Noeq
|
|
8
15
|
ids
|
9
16
|
end
|
10
17
|
|
11
|
-
|
12
|
-
|
18
|
+
# `Noeq.new` defaults to connecting to `localhost:4444` with async off.
|
19
|
+
# The `options` hash is used so that we are verbose when turning async on.
|
20
|
+
def initialize(host = 'localhost', port = 4444, options = {})
|
21
|
+
@host, @port, @async = host, port, options[:async]
|
13
22
|
connect
|
14
23
|
end
|
15
24
|
|
25
|
+
# The first thing that we need to do is connect to the `noeqd` server.
|
16
26
|
def connect
|
17
|
-
|
27
|
+
# We create a new TCP `STREAM` socket. There are a few other types of
|
28
|
+
# sockets, but this is the most common.
|
29
|
+
@socket = Socket.new(:INET, :STREAM)
|
30
|
+
|
31
|
+
# In order to create a socket connection we need an address object.
|
32
|
+
address = Socket.sockaddr_in(@port, @host)
|
33
|
+
|
34
|
+
# If async is enabled, we establish the connection in nonblocking mode,
|
35
|
+
# otherwise we connect normally, which will wait until the connection is
|
36
|
+
# established.
|
37
|
+
@async ? @socket.connect_nonblock(address) : @socket.connect(address)
|
38
|
+
|
39
|
+
# `Socket.connect_nonblock` raises `Errno::EINPROGRESS` if the socket isn't
|
40
|
+
# connected instantly. It will be connected in the background, so we ignore
|
41
|
+
# the exception
|
42
|
+
rescue Errno::EINPROGRESS
|
18
43
|
end
|
19
44
|
|
20
45
|
def disconnect
|
46
|
+
# If the socket has already been closed by the other side, `close` will
|
47
|
+
# raise, so we rescue it.
|
21
48
|
@socket.close rescue false
|
22
49
|
end
|
23
50
|
|
51
|
+
# The workhorse generate method. Defaults to one id, but up to 255 can be
|
52
|
+
# requested.
|
24
53
|
def generate(n=1)
|
25
|
-
|
26
|
-
|
27
|
-
|
54
|
+
request_id(n)
|
55
|
+
fetch_id(n)
|
56
|
+
|
57
|
+
# If something goes wrong, we reconnect and retry. There is a slim chance
|
58
|
+
# that this will result in an infinite loop, but most errors are raised in
|
59
|
+
# the reconnect step and won't get re-rescued here.
|
28
60
|
rescue
|
29
61
|
disconnect
|
30
62
|
connect
|
31
63
|
retry
|
32
64
|
end
|
33
65
|
|
66
|
+
def request_id(n=1)
|
67
|
+
# The integer is packed into a binary byte and sent to the `noeqd` server.
|
68
|
+
# The second argument to `BasicSocket#send` is a bitmask of flags, we don't
|
69
|
+
# need anything special, so it is set to zero.
|
70
|
+
@socket.send [n].pack('c'), 0
|
71
|
+
end
|
72
|
+
alias :request_ids :request_id
|
73
|
+
|
74
|
+
def fetch_id(n=1)
|
75
|
+
# We collect the ids from the `noeqd` server.
|
76
|
+
ids = (1..n).map { get_id }.compact
|
77
|
+
|
78
|
+
# If we have more than one id, we return the array, otherwise we return the
|
79
|
+
# single id.
|
80
|
+
ids.length > 1 ? ids : ids.first
|
81
|
+
end
|
82
|
+
alias :fetch_ids :fetch_id
|
83
|
+
|
34
84
|
private
|
35
85
|
|
36
86
|
def get_id
|
37
|
-
|
87
|
+
# `noeqd` sends us a 64-bit unsigned integer in network (big-endian) byte
|
88
|
+
# order, but Ruby 1.8 doesn't have a native unpack directive for this, so we
|
89
|
+
# do it manually by shifting the high bits and adding the low bits.
|
90
|
+
high, low = read_long, read_long
|
91
|
+
return unless high && low
|
92
|
+
(high << 32) + low
|
38
93
|
end
|
39
94
|
|
40
95
|
def read_long
|
41
|
-
|
96
|
+
# `IO.select` blocks until one of the sockets passed in has an event
|
97
|
+
# or a timeout is reached (the fourth argument). We don't do the `select`
|
98
|
+
# if we are in async mode.
|
99
|
+
IO.select([@socket], nil, nil, 0.1) unless @async
|
100
|
+
|
101
|
+
# Since `select` has already blocked for us, we are pretty sure that
|
102
|
+
# there is data available on the socket, so we try to fetch 4 bytes and
|
103
|
+
# unpack them as a 32-bit big-endian unsigned integer. If there is no data
|
104
|
+
# available this will raise `Errno::EAGAIN` which will propagate up and
|
105
|
+
# could cause a retry.
|
106
|
+
@socket.recv_nonblock(4).unpack("N").first
|
42
107
|
end
|
43
108
|
end
|
data/noeq.gemspec
CHANGED
data/test/noeq_test.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require './lib/noeq'
|
3
|
+
|
4
|
+
class NoeqTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
FakeNoeqd.start
|
8
|
+
end
|
9
|
+
|
10
|
+
def teardown
|
11
|
+
FakeNoeqd.stop
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_simple_generate
|
15
|
+
assert_equal expected_id, Noeq.generate
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_multiple_generate
|
19
|
+
noeq = Noeq.new
|
20
|
+
assert_equal [expected_id]*3, noeq.generate(3)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_different_port
|
24
|
+
FakeNoeqd.stop
|
25
|
+
FakeNoeqd.start(4545)
|
26
|
+
|
27
|
+
noeq = Noeq.new('localhost', 4545)
|
28
|
+
assert_equal expected_id, noeq.generate
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_reconnect
|
32
|
+
noeq = Noeq.new
|
33
|
+
assert noeq.generate
|
34
|
+
|
35
|
+
FakeNoeqd.stop
|
36
|
+
FakeNoeqd.start
|
37
|
+
|
38
|
+
assert_equal expected_id, noeq.generate
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_async_generate
|
42
|
+
noeq = Noeq.new('localhost', 4444, :async => true)
|
43
|
+
noeq.request_id
|
44
|
+
sleep 0.0001
|
45
|
+
assert_equal expected_id, noeq.fetch_id
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_async_request_with_disconnected_server_raises
|
49
|
+
noeq = Noeq.new('localhost', 4444, :async => true)
|
50
|
+
FakeNoeqd.stop
|
51
|
+
assert_raises(Errno::EPIPE) { noeq.request_id }
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def expected_id
|
57
|
+
144897448664367104
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
class FakeNoeqd
|
63
|
+
|
64
|
+
def self.start(port = 4444)
|
65
|
+
@server = new(port)
|
66
|
+
Thread.new { @server.accept_connections }
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.stop
|
70
|
+
@server.stop
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialize(port)
|
74
|
+
@socket = TCPServer.new(port)
|
75
|
+
end
|
76
|
+
|
77
|
+
def stop
|
78
|
+
@socket.close rescue true
|
79
|
+
end
|
80
|
+
|
81
|
+
def accept_connections
|
82
|
+
while conn = @socket.accept
|
83
|
+
while n = conn.read(1)
|
84
|
+
conn.send "\x02\x02\xC7v<\x80\x00\x00" * n.unpack('c')[0], 0
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: noeq
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.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: 2011-12-
|
12
|
+
date: 2011-12-09 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Ruby noeqd GUID client
|
15
15
|
email:
|
@@ -22,8 +22,10 @@ files:
|
|
22
22
|
- LICENSE
|
23
23
|
- README.md
|
24
24
|
- Rakefile
|
25
|
+
- TODO
|
25
26
|
- lib/noeq.rb
|
26
27
|
- noeq.gemspec
|
28
|
+
- test/noeq_test.rb
|
27
29
|
homepage: http://github.com/titanous/noeq-rb
|
28
30
|
licenses: []
|
29
31
|
post_install_message:
|
@@ -48,4 +50,5 @@ rubygems_version: 1.8.11
|
|
48
50
|
signing_key:
|
49
51
|
specification_version: 3
|
50
52
|
summary: Ruby noeqd GUID client
|
51
|
-
test_files:
|
53
|
+
test_files:
|
54
|
+
- test/noeq_test.rb
|