plux 0.1.2 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a48ed6a3720e61c2bce1d3b181f93b2b8c290019c7feb076ca2398a4c0b6f203
4
- data.tar.gz: 67d7f993aeba391f3986e284c38846e5bc4afb3305b475e390969c3f3dca5c09
3
+ metadata.gz: fe3a01341377759b20ec857779769a121787a2caa79d1f06c33871e779a4a2db
4
+ data.tar.gz: bdd9a7988dd40776d31b62989b59191b3474436f6a0a847869eb1b568de59303
5
5
  SHA512:
6
- metadata.gz: 10539dd311f59a391218943e159d389b99033239136f6aeb04137f39e3d563c5fb5af287d907a708bb1f4cbbfec942e3a1e4d053c64e92147f91ca121b3c1559
7
- data.tar.gz: eab024ca828276eebc07118249bd7d9493dde24988f14bd7df71f367d865a5a7580fdc7dc5fdd97e6ee119b56d9cb43f98f9bd81ce0dd58cd74932719c46bed9
6
+ metadata.gz: 1dda3d9c1d45d6acd3af4f8cdba9e3ba954b5d3c8ac0c35cada18ffcf89c08bfe7aabdf31ecb46de30c304a57a78c8762d1b66694244f0b7252974c12337fac5
7
+ data.tar.gz: 142cff4debc8825f8911d2c542d2f03e13a04d49d9a81a8cf414f313073d5be9eb67b608d58c8d03be0ec0a069d4db2c95b1c957769860bb7a841edb6abe05a6
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- plux (0.1.0)
4
+ plux (0.1.6)
5
+ nio4r (~> 2.0)
5
6
 
6
7
  GEM
7
8
  remote: https://rubygems.org/
@@ -9,6 +10,7 @@ GEM
9
10
  coderay (1.1.2)
10
11
  method_source (0.9.2)
11
12
  minitest (5.14.1)
13
+ nio4r (2.5.4)
12
14
  pry (0.12.2)
13
15
  coderay (~> 1.1.0)
14
16
  method_source (~> 0.9.0)
data/README.md CHANGED
@@ -21,32 +21,57 @@ Or install it yourself as:
21
21
  ## Usage
22
22
 
23
23
  ```ruby
24
- # start one and only process named 'abc',
24
+ # start one and only process named 'abc', with 2 threads in it (1 thread if not specified),
25
25
  # no matter the code below is called how many times in whatever processes/threads
26
- server = Plux.worker(:abc) do
26
+ server = Plux.worker(:abc, thread: 2) do
27
27
 
28
- # prepare resources like mq/db, to handle requests
29
- def initialize
28
+ # prepare thread-safe resources like mq/db, to handle requests
29
+ def prepare
30
30
  # @db = ...
31
31
  end
32
32
 
33
33
  # threads call this method to deal with clients' message
34
- def work(msg)
34
+ def process(msg)
35
35
  # @db << parse(msg)
36
36
  end
37
37
  end
38
38
 
39
- # five threads will be started to handle these clients,
40
- # and finished once their counterparts call close
39
+ # clients connect, send msg, and close concurrently
41
40
  5.times do |n|
42
41
  Thread.new do
43
42
  client = server.connect
44
- client.puts "hello #{n}"
43
+ client.puts "hello"
44
+ client.puts "my name is #{n}"
45
45
  client.close
46
46
  end
47
47
  end
48
48
  ```
49
49
 
50
+ Also, you can boot multiple servers(processes) with same logic:
51
+
52
+ ```ruby
53
+ class AwesomeServer < Plux::Engine
54
+ # prepare thread-safe resources like mq/db, to handle requests
55
+ def prepare
56
+ # @db = ...
57
+ end
58
+
59
+ # threads call this method to deal with clients' message
60
+ def process(msg)
61
+ # @db << parse(msg)
62
+ end
63
+ end
64
+
65
+ a = AwesomeServer.new(:a)
66
+ b = AwesomeServer.new(:b)
67
+ c = AwesomeServer.new(:c, thread: 4)
68
+
69
+ a_client = a.connect
70
+ b_client = b.connect
71
+ c_client = c.connect
72
+ c_client_2 = c.connect
73
+ ```
74
+
50
75
  ## Development
51
76
 
52
77
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -3,6 +3,9 @@ require "socket"
3
3
  require "plux/version"
4
4
  require "plux/server"
5
5
  require "plux/client"
6
+ require "plux/parser"
7
+ require "plux/engine"
8
+
6
9
 
7
10
  module Plux
8
11
 
@@ -30,10 +33,12 @@ module Plux
30
33
  File.join(dir, "#{server_name}.so")
31
34
  end
32
35
 
33
- def worker(name, &block)
34
- Server.new(name).boot(block)
36
+ def worker(name, thread: 1, &block)
37
+ worker = Class.new(&block).new
38
+ Server.new(name, thread: thread).boot(worker)
35
39
  end
36
40
  end
37
41
 
38
42
  FileUtils.mkdir_p(self.dir)
43
+
39
44
  end
@@ -4,18 +4,17 @@ module Plux
4
4
 
5
5
  def initialize(server_name)
6
6
  @server_name = server_name
7
- UNIXSocket.open(Plux.server_file(server_name)) do |c|
8
- reader, @writer = IO.pipe
9
- c.send_io(reader)
10
- reader.close
11
- end
7
+ @writer = UNIXSocket.open(Plux.server_file(server_name))
12
8
  end
13
9
 
14
- def puts(arg)
15
- @writer.puts(arg)
10
+ def puts(msg)
11
+ Parser.encode(msg).each do |sub_msg|
12
+ @writer.write(sub_msg)
13
+ end
16
14
  end
17
15
 
18
16
  def close
17
+ puts(Parser::LAST_MSG)
19
18
  @writer.close
20
19
  end
21
20
  end
@@ -0,0 +1,18 @@
1
+ module Plux
2
+ class Engine
3
+
4
+ def initialize(name, thread: 1)
5
+ @server = Server.new(name, thread: thread).boot(self)
6
+ end
7
+
8
+ def connect
9
+ @server.connect
10
+ end
11
+
12
+ def prepare
13
+ end
14
+
15
+ def process(msg)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,67 @@
1
+ module Plux
2
+ class Parser
3
+
4
+ SYSTEM = 36
5
+ SEPERATOR = '.'.freeze
6
+ STREAM_MAX_LEN = 4096
7
+ LAST_MSG = ''.freeze
8
+
9
+ def initialize
10
+ @broken = ''
11
+ end
12
+
13
+ def decode(stream)
14
+ stream = @broken.concat(stream)
15
+ result = []
16
+
17
+ start = 0
18
+ pending = stream.length
19
+
20
+ until start >= pending
21
+ sep_idx = stream.index(SEPERATOR, start)
22
+
23
+ unless sep_idx
24
+ len_str = stream[start, pending]
25
+ @broken.clear
26
+ @broken.concat(len_str)
27
+ return result
28
+ end
29
+
30
+ len_str = stream[start, sep_idx - start]
31
+ est_len = len_str.to_i(SYSTEM)
32
+ start = sep_idx + 1
33
+ msg = stream[start, est_len]
34
+ act_len = msg.length
35
+
36
+ if act_len < est_len
37
+ @broken.clear
38
+ @broken.concat(len_str)
39
+ @broken.concat(SEPERATOR)
40
+ @broken.concat(msg)
41
+ return result
42
+ end
43
+
44
+ result << msg
45
+ start += act_len
46
+ end
47
+
48
+ @broken.clear
49
+ result
50
+ end
51
+
52
+ def self.encode(msg)
53
+ len = msg.length.to_s(SYSTEM)
54
+
55
+ msg = "#{len}#{SEPERATOR}#{msg}"
56
+ len = msg.length
57
+
58
+ return [msg] if len < STREAM_MAX_LEN
59
+
60
+ div, mod = len.divmod(STREAM_MAX_LEN)
61
+ (mod > 0 ? (div + 1) : div).times.map do |start|
62
+ msg[start * STREAM_MAX_LEN, STREAM_MAX_LEN]
63
+ end
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,76 @@
1
+ module Plux
2
+ class Reactor
3
+ def initialize(count, worker)
4
+ @worker = worker
5
+ @msg_q = Queue.new
6
+ @count = count
7
+
8
+ @nio = NIO::Selector.new
9
+ @newly_accepted = Queue.new
10
+ @closed = []
11
+
12
+ receive
13
+ process
14
+ end
15
+
16
+ def register(socket)
17
+ @newly_accepted << socket
18
+ @nio.wakeup
19
+ end
20
+
21
+ private
22
+
23
+ def receive
24
+ Thread.new do
25
+ loop do
26
+ @closed.size.times{ @nio.deregister(@closed.pop) }
27
+
28
+ @newly_accepted.size.times do
29
+ socket = @newly_accepted.pop
30
+ mon = @nio.register(socket, :r)
31
+ mon.value = Worker.new(socket, @msg_q)
32
+ end
33
+
34
+ @nio.select do |m|
35
+ next if m.value.process
36
+ @closed << m.io
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ def process
43
+ @count.times.each do
44
+ Thread.new do
45
+ loop{ @worker.process(@msg_q.deq) }
46
+ end
47
+ end
48
+ end
49
+
50
+ class Worker
51
+ def initialize(socket, q)
52
+ @parser = Parser.new
53
+ @socket = socket
54
+ @q = q
55
+ end
56
+
57
+ def process
58
+ stream = @socket.read_nonblock(Parser::STREAM_MAX_LEN, exception: false)
59
+ return true if stream == :wait_readable
60
+
61
+ msgs = @parser.decode(stream)
62
+ last_msg = msgs.pop
63
+
64
+ msgs.each{ |msg| @q << msg }
65
+ if last_msg == Parser::LAST_MSG
66
+ @socket.close
67
+ return false
68
+ end
69
+ @q << last_msg
70
+
71
+ true
72
+ end
73
+ end
74
+
75
+ end
76
+ end
@@ -1,3 +1,6 @@
1
+ require "nio"
2
+ require "plux/reactor"
3
+
1
4
  module Plux
2
5
 
3
6
  class Server
@@ -6,13 +9,14 @@ module Plux
6
9
  Active = {}
7
10
  at_exit{ Active.values.each(&:close) }
8
11
 
9
- def initialize(name)
12
+ def initialize(name, thread: )
10
13
  @name = name
14
+ @thread = thread
11
15
  end
12
16
 
13
- def boot(block)
17
+ def boot(worker)
14
18
  Plux.lock_pid_file(name) do |file|
15
- start_server_if_not_pid(file, block)
19
+ start_server_if_not_pid(file, worker)
16
20
  end
17
21
  self
18
22
  end
@@ -27,7 +31,7 @@ module Plux
27
31
 
28
32
  private
29
33
 
30
- def start_server_if_not_pid(file, block)
34
+ def start_server_if_not_pid(file, worker)
31
35
  @pid = file.read.to_i
32
36
  return unless pid == 0
33
37
 
@@ -39,11 +43,9 @@ module Plux
39
43
  child.close
40
44
  UNIXServer.open(Plux.server_file(name)) do |serv|
41
45
  parent.close
42
- worker = Class.new(&block).new
43
- loop do
44
- socket = serv.accept
45
- Worker.new(socket, worker)
46
- end
46
+ worker.prepare
47
+ reactor = Reactor.new(@thread, worker)
48
+ loop{ reactor.register(serv.accept) }
47
49
  end
48
50
  end
49
51
 
@@ -62,20 +64,6 @@ module Plux
62
64
  File.delete(Plux.send(file, name))
63
65
  end
64
66
  end
65
-
66
- class Worker
67
- def initialize(socket, worker)
68
- t = Thread.new do
69
- client = socket.recv_io
70
- socket.close
71
- while line = client.gets
72
- worker.work(line)
73
- end
74
- client.close
75
- end
76
- end
77
- end
78
-
79
67
  end
80
68
 
81
69
  end
@@ -1,3 +1,3 @@
1
1
  module Plux
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.7"
3
3
  end
@@ -23,4 +23,6 @@ Gem::Specification.new do |spec|
23
23
  spec.add_development_dependency "rake", "~> 10.0"
24
24
  spec.add_development_dependency "minitest", "~> 5.0"
25
25
  spec.add_development_dependency "pry"
26
+
27
+ spec.add_dependency "nio4r", "~> 2.0"
26
28
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plux
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - ken
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-09-09 00:00:00.000000000 Z
11
+ date: 2020-09-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: nio4r
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.0'
69
83
  description:
70
84
  email:
71
85
  - block24block@gmail.com
@@ -85,6 +99,9 @@ files:
85
99
  - bin/setup
86
100
  - lib/plux.rb
87
101
  - lib/plux/client.rb
102
+ - lib/plux/engine.rb
103
+ - lib/plux/parser.rb
104
+ - lib/plux/reactor.rb
88
105
  - lib/plux/server.rb
89
106
  - lib/plux/version.rb
90
107
  - plux.gemspec