io_daemonizer 3 → 6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/io_daemonizer.rb +79 -16
  3. metadata +5 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2fe90c198a168ddb60ba8664074e8ba81eb4e08fbfac69e86af72edea74e937f
4
- data.tar.gz: a386e4e1e76bbf154944d579d9220a35bfe7123d4725ef20bc22a2c3950a3fd1
3
+ metadata.gz: 9221b86c37a20ab3d79c7fb1fee60e9a7c74aa083d3aff30da43e755c9a02256
4
+ data.tar.gz: f0b41de14e06824a163ded73f2b70d45bc54c1c086ee8c2419b83af4d1ab3c58
5
5
  SHA512:
6
- metadata.gz: 2804cf8d50674070fe9bd9c6c8fb951bd6868acd7ee01a3862963b6e22f122ad09ea56113e892e93185a0d2caf58a30dcf24b570bcfbce86db62cc35f0a389d9
7
- data.tar.gz: 291594bc93f1a5b269c8a668815ff3bc9f1ebc18cb01c164d014307701e1d72224e89b950b5ef1a4b02b7cdc655a22770ad90fda506b140357be90403d289df7
6
+ metadata.gz: cbcfd15d8a147dc074f4003e7d1a6f2ee17979644220abb54044b740c7023f0f51bab528c395b38b053efb685abac807c4648387b9aa5c1e3cad0d0e45f0ef5f
7
+ data.tar.gz: 250baadd6ca2b943920408f431a66b5920f1655cfd44b4026f7c08409b23dcf9ad19972e2aae7f900e09268d4f9c472c33e80455a62ddf982e78751da9433f44
data/io_daemonizer.rb CHANGED
@@ -1,33 +1,44 @@
1
- # io_daemonizer v.3 https://github.com/joeyschoblaska/io_daemonizer
1
+ # io_daemonizer v.6 https://github.com/joeyschoblaska/io_daemonizer
2
2
 
3
+ require "json"
3
4
  require "shellwords"
4
5
  require "socket"
5
6
  require "stringio"
6
7
 
7
8
  class IODaemonizer
8
- PORT = ENV["IO_DAEMONIZER_PORT"] || 5289
9
-
10
- def self.wrap(setup: -> {}, run: -> {})
9
+ def self.wrap(port:, setup:, run:, autostart: true)
11
10
  case ARGV[0]
12
11
  when "start"
13
12
  puts "starting server..."
14
- Daemon.run(setup: setup, run: run)
13
+ Daemon.run(port: port, setup: setup, run: run)
15
14
  when "stop"
16
15
  puts "stopping server..."
17
- send_request(ARGV)
16
+ send_request(port: port, args: ARGV)
18
17
  else
19
- send_request(ARGV)
18
+ begin
19
+ send_request(port: port, args: ARGV)
20
+ rescue Errno::ECONNREFUSED => e
21
+ raise(e) unless autostart
22
+ daemon = Daemon.new(port: port, setup: setup, run: run)
23
+ daemon.setup
24
+ fork { daemon.start }
25
+ sleep 0.1
26
+ send_request(port: port, args: ARGV)
27
+ end
20
28
  end
21
29
  rescue Errno::ECONNREFUSED
22
30
  puts "server not running or not responding"
23
31
  end
24
32
 
25
- def self.send_request(args)
26
- TCPSocket.open("127.0.0.1", PORT) do |socket|
33
+ def self.send_request(port:, args:)
34
+ consumer = LabeledIOConsumer.new
35
+
36
+ TCPSocket.open("127.0.0.1", port) do |socket|
27
37
  socket.puts args.shelljoin
28
38
  socket.write $stdin.tty? ? "" : $stdin.read
29
39
  socket.close_write
30
- STDOUT.write(socket.read(4096)) until socket.eof?
40
+
41
+ consumer.write(socket.read) until socket.eof?
31
42
  end
32
43
  end
33
44
 
@@ -41,13 +52,14 @@ class IODaemonizer
41
52
  end
42
53
 
43
54
  class Daemon
44
- def self.run(setup: -> {}, run: -> {})
45
- daemon = new(setup: setup, run: run)
55
+ def self.run(port:, setup:, run:)
56
+ daemon = new(port: port, setup: setup, run: run)
46
57
  daemon.setup
47
58
  daemon.start
48
59
  end
49
60
 
50
- def initialize(setup: -> {}, run: -> {})
61
+ def initialize(port:, setup:, run:)
62
+ @port = port
51
63
  @setup = setup
52
64
  @run = run
53
65
  @context = Object.new
@@ -58,7 +70,7 @@ class IODaemonizer
58
70
  end
59
71
 
60
72
  def start
61
- @server = TCPServer.open("127.0.0.1", PORT)
73
+ @server = TCPServer.open("127.0.0.1", @port)
62
74
  Process.daemon(true)
63
75
  read_socket(@server.accept) until @server.closed?
64
76
  end
@@ -74,14 +86,65 @@ class IODaemonizer
74
86
  else
75
87
  IODaemonizer.redirect(
76
88
  stdin: StringIO.new(body.join),
77
- stdout: socket,
78
- stderr: socket
89
+ stdout: IOLabeler.new(1, socket, "stdout"),
90
+ stderr: IOLabeler.new(2, socket, "stderr")
79
91
  ) { @context.instance_exec args, &@run }
80
92
  end
81
93
  rescue => e
82
94
  socket.write e.inspect
95
+ raise e
83
96
  ensure
97
+ socket.close_write
84
98
  socket.close
85
99
  end
86
100
  end
101
+
102
+ class IOLabeler < IO
103
+ attr_reader :label
104
+
105
+ def initialize(fd, socket, label)
106
+ super(fd)
107
+ @socket = socket
108
+ @label = label
109
+ end
110
+
111
+ def write(chunk)
112
+ @socket.write({ @label => chunk }.to_json)
113
+ end
114
+
115
+ def reopen(io)
116
+ @label = io&.label || @label
117
+ end
118
+ end
119
+
120
+ class LabeledIOConsumer
121
+ def initialize
122
+ @buffer = ""
123
+ end
124
+
125
+ def write(chunk)
126
+ chunk.chars.each do |ch|
127
+ @buffer << ch
128
+ process_buffer
129
+ end
130
+ end
131
+
132
+ private
133
+
134
+ def process_buffer
135
+ parsed = JSON.parse(@buffer)
136
+ key = parsed.keys[0]
137
+ value = parsed.values[0]
138
+
139
+ case key
140
+ when "stdout"
141
+ $stdout.write(value)
142
+ when "stderr"
143
+ $stderr.write(value)
144
+ end
145
+
146
+ @buffer = ""
147
+ rescue JSON::ParserError
148
+ end
149
+ end
87
150
  end
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: io_daemonizer
3
3
  version: !ruby/object:Gem::Version
4
- version: '3'
4
+ version: '6'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joey Schoblaska
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-06 00:00:00.000000000 Z
11
+ date: 2022-09-07 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: Wrap a Ruby script that speaks IO in a daemon so that you only pay the
14
- startup overhead once.
13
+ description: Wrap a Ruby script that speaks stdio in a daemon so that you only pay
14
+ the startup overhead once.
15
15
  email: joey.schoblaska@gmail.com
16
16
  executables: []
17
17
  extensions: []
@@ -37,7 +37,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
37
37
  - !ruby/object:Gem::Version
38
38
  version: '0'
39
39
  requirements: []
40
- rubygems_version: 3.3.3
40
+ rubygems_version: 3.2.3
41
41
  signing_key:
42
42
  specification_version: 4
43
43
  summary: IO Daemonizer