wires-cluster 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []