wires-cluster 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 52d376216621589ebc6244e42f99969ca1762986
4
+ data.tar.gz: b55d81e07aebad4eaf6e31e0440cffe8596aec4d
5
+ SHA512:
6
+ metadata.gz: 7456c1ac652e1dcd87e0768c90d09f531cf023a608846274421083376be8367136259cdb29e4aee9387609e0a8fcbd187e7444a077d48f4c208b3ab7de6f16e0
7
+ data.tar.gz: 23a1faf0eaab0db74c549c6df870ce64e79dcb8c83966f85279c62107f9b02eb07f09c88728fcebae0a98d161f6f0a7211b19f5596a52236f65da32266fa1d74
data/LICENSE ADDED
@@ -0,0 +1,2 @@
1
+ Copyright (c) 2013 : Joe McIlvain
2
+ All rights reserved.
@@ -0,0 +1,78 @@
1
+
2
+ require 'wires'
3
+ require "#{File.dirname __FILE__}/cluster/udp"
4
+ require "#{File.dirname __FILE__}/cluster/json"
5
+
6
+
7
+ module Wires
8
+ module Cluster
9
+ PORT = 4567
10
+ GROUP = "224.0.1.33"
11
+
12
+ class << self
13
+
14
+ # Block indefinitely while receiving messages
15
+ def listen!
16
+ listen
17
+ @rx_thread.join
18
+ end
19
+
20
+ # Start the UDP receiving thread (or stop it, if action==:stop)
21
+ def listen(action=:start)
22
+ case action
23
+ when :start
24
+ @rx ||= UDP::RX.new GROUP, PORT
25
+ raise IOError, "Probably firewalled..." unless @rx.test!
26
+ @rx_thread ||= Thread.new { _rx_loop }
27
+ when :stop
28
+ @rx_thread.kill; @rx_thread = nil
29
+ @rx.close; @rx = nil
30
+ end
31
+ end
32
+
33
+ # Enable UDP sending of fired events (or disable, if action==:stop)
34
+ def spout(action=:start)
35
+ case action
36
+ when :start
37
+ @tx ||= UDP::TX.new GROUP, PORT
38
+ @tx_proc = Wires::Channel.after_fire(true) do |e,c|
39
+ @tx.puts JSON.dump [e,c] unless e.cluster_source
40
+ end
41
+ when :stop
42
+ Wires::Channel.remove_hook :@after_fire, @tx_proc
43
+ @tx.close; @tx = nil
44
+ end
45
+ end
46
+
47
+ # Loop through incoming messages and deploy valid firable events
48
+ def _rx_loop
49
+ ongoing = {}
50
+ loop do
51
+ msg = @rx.gets
52
+ ongoing[msg.source] ||= ''
53
+ ongoing[msg.source] += msg
54
+
55
+ begin
56
+ data = _load_json(ongoing[msg.source])
57
+ if (data[0].is_a? Event) and (data[1].is_a? Channel)
58
+ event, chan = data
59
+ event.instance_variable_set(:@cluster_source, msg.source)
60
+ Convenience::fire *data
61
+ end
62
+ ongoing[msg.source] = nil
63
+ rescue JSON::MissingTailError
64
+ rescue JSON::ParserError
65
+ ongoing[msg.source] = nil
66
+ end
67
+
68
+ end
69
+ end; private :_rx_loop
70
+
71
+ end
72
+
73
+ end
74
+ end
75
+
76
+ class Wires::Event
77
+ attr_reader :cluster_source
78
+ end
@@ -0,0 +1,99 @@
1
+
2
+ # For now, must require in this order
3
+ # to avoid active_support wrecking json/add/core
4
+ require 'active_support/json'
5
+ require 'json/add/core'
6
+
7
+ # Add for reference when parsing for incomplete packets
8
+ require 'json/pure/parser'
9
+
10
+
11
+ # Define default JSON behavior for objects as UnserializableObject
12
+ class Object
13
+ def as_json(opt=nil)
14
+ UnserializableObject.new.as_json
15
+ end
16
+
17
+ def self.json_create(data)
18
+ UnserializableObject.new
19
+ end
20
+ end
21
+
22
+ # A placeholder object to alert the JSON receiver that
23
+ # the intended object has no defined serialization scheme
24
+ class UnserializableObject
25
+ def to_s; '<UnserializableObject>'; end
26
+ def inspect; to_s; end
27
+
28
+ def as_json(opt=nil)
29
+ { json_class:self.class.name }
30
+ end
31
+
32
+ def self.json_create(data)
33
+ self.new
34
+ end
35
+ end
36
+
37
+
38
+ # Define a serialization scheme for Wires Events
39
+ class Wires::Event
40
+ def as_json(*serialization_args)
41
+ { json_class: self.class.name,
42
+ args: [*@args, **@kwargs] }
43
+ end
44
+
45
+ def self.json_create(data)
46
+ self.new(*data['args'])
47
+ end
48
+ end
49
+
50
+ # Define a serialization scheme for Wires Channels
51
+ class Wires::Channel
52
+ def as_json(*serialization_args)
53
+ { json_class: self.class.name,
54
+ name: @name }
55
+ end
56
+
57
+ def self.json_create(data)
58
+ self.new(data['name'])
59
+ end
60
+ end
61
+
62
+
63
+ # Add JSON-related functions to Wires::Cluster
64
+ module Wires
65
+ module Cluster
66
+ class << self
67
+
68
+ # Perform rudimentary JSON verification to make sure str is not
69
+ # a partial payload due to missing or yet-to-come packets,
70
+ # then actually load in the JSON objects and return them
71
+ def _load_json(str)
72
+ ref_parser = JSON::Pure::Parser
73
+
74
+ # Ignore json comments and strings for open/close count verification
75
+ stripped_str = str.gsub(ref_parser::IGNORE, '')
76
+ .gsub(ref_parser::STRING, '')
77
+
78
+ # Missing tail if any quote symbols remain after string purge
79
+ raise JSON::MissingTailError if stripped_str.match /(?<!\\)"/
80
+
81
+ # Make sure open/close symbol counts match
82
+ [[ref_parser::OBJECT_OPEN, ref_parser::OBJECT_CLOSE],
83
+ [ref_parser::ARRAY_OPEN, ref_parser::ARRAY_CLOSE]].each do |a, z|
84
+ case (stripped_str.scan(a).size) <=> (stripped_str.scan(z).size)
85
+ when 1; raise JSON::MissingTailError
86
+ when -1; raise JSON::MissingHeadError
87
+ end
88
+ end
89
+
90
+ # Try to load objects from the string and return the result
91
+ JSON.load(str)
92
+ end; private :_load_json
93
+
94
+ end
95
+ end
96
+ end
97
+
98
+ class JSON::MissingTailError < JSON::ParserError; end
99
+ class JSON::MissingHeadError < JSON::ParserError; end
@@ -0,0 +1,115 @@
1
+
2
+ require 'socket'
3
+ require 'ipaddr'
4
+
5
+ module Wires
6
+ module Cluster
7
+ module UDP
8
+
9
+ class << self; attr_accessor :max_length; end
10
+ self.max_length = 1024 # default max payload length
11
+
12
+ class Xceiver
13
+ attr_reader :group, :port, :socket, :local_ip, :local_port
14
+
15
+ def self.new(*args)
16
+ if (self.class==Xceiver)
17
+ raise TypeError, "#{self.class} is an 'abstract class' only."\
18
+ " Inherit it; don't instantiate it!"; end
19
+ super
20
+ end
21
+
22
+ def initialize(group, port, **kwargs)
23
+ @group = group
24
+ @port = port
25
+ kwargs.each_pair { |k,v| instance_variable_set("@#{k}".to_sym, v) }
26
+ open
27
+ end
28
+
29
+ def open
30
+ @socket.close if @socket
31
+ @socket = UDPSocket.new
32
+ configure
33
+ @local_ip = Socket.ip_address_list.detect{|intf| intf.ipv4_private?}
34
+ @local_port = @socket.addr[1]
35
+ return @socket
36
+ ensure
37
+ @finalizer ||= ObjectSpace.define_finalizer self, Proc.new { close }
38
+ end
39
+
40
+ def close
41
+ @socket.close if @socket
42
+ @socket = nil
43
+ end
44
+ end
45
+
46
+
47
+ class TX < Xceiver
48
+ def configure
49
+ # Set up for multicasting
50
+ @socket.setsockopt Socket::IPPROTO_IP,
51
+ Socket::IP_MULTICAST_TTL,
52
+ [1].pack('i')
53
+ # Bind to any available port
54
+ @socket.bind "0.0.0.0", (@bind_port or 0)
55
+ end
56
+
57
+ def puts(m)
58
+ max = UDP.max_length
59
+ if m.size > max
60
+ self.puts m[0...max]
61
+ self.puts m[max...m.size]
62
+ else
63
+ @socket.send(m, 0, @group, @port)
64
+ m
65
+ end
66
+ end
67
+ end
68
+
69
+
70
+ class RX < Xceiver
71
+ def configure
72
+ # Add membership to the multicast group
73
+ @socket.setsockopt Socket::IPPROTO_IP,
74
+ Socket::IP_ADD_MEMBERSHIP,
75
+ IPAddr.new(@group).hton + IPAddr.new("0.0.0.0").hton
76
+ # Don't prevent future listening peers on the same machine
77
+ @socket.setsockopt(Socket::SOL_SOCKET,
78
+ Socket::SO_REUSEADDR,
79
+ [1].pack('i')) unless @selfish
80
+ # Bind the socket to the specified port or any open port on the machine
81
+ @socket.bind Socket::INADDR_ANY, @port
82
+ end
83
+
84
+ def gets
85
+ msg, addrinfo = @socket.recvfrom(UDP.max_length)
86
+ msg.instance_variable_set :@source, addrinfo[3].to_s+':'+addrinfo[1].to_s
87
+ class << msg; attr_reader :source; end
88
+ msg
89
+ end
90
+
91
+ def test!(message="#{self}.test!")
92
+ tx = UDP::TX.new @group, @port
93
+ rx = self
94
+
95
+ outer_thread = Thread.current
96
+ passed = false
97
+ thr = Thread.new do
98
+ rx.gets
99
+ passed = true
100
+ outer_thread.wakeup
101
+ end
102
+ Thread.pass
103
+
104
+ tx.puts message
105
+ sleep 1 if thr.status
106
+ thr.kill
107
+ tx.close
108
+
109
+ passed
110
+ end
111
+ end
112
+
113
+ end
114
+ end
115
+ end
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wires-cluster
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Joe McIlvain
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-09-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: wires
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: wires-test
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: jemc-reporter
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Wires extension gem for firing and receiving events between Ruby processes
84
+ with UDP multicasts.
85
+ email: joe.eli.mac@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - lib/wires/cluster/udp.rb
91
+ - lib/wires/cluster/json.rb
92
+ - lib/wires/cluster.rb
93
+ - LICENSE
94
+ homepage: https://github.com/jemc/wires-cluster/
95
+ licenses:
96
+ - 'Copyright (c) Joe McIlvain. All rights reserved '
97
+ metadata: {}
98
+ post_install_message:
99
+ rdoc_options: []
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - '>='
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - '>='
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ requirements: []
113
+ rubyforge_project:
114
+ rubygems_version: 2.0.3
115
+ signing_key:
116
+ specification_version: 4
117
+ summary: wires-cluster
118
+ test_files: []