aggkit 0.2.5

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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +20 -0
  3. data/.gitignore +109 -0
  4. data/.gitlab-ci.yml +66 -0
  5. data/.rspec +4 -0
  6. data/.rubocop.yml +98 -0
  7. data/.travis.yml +30 -0
  8. data/Gemfile +12 -0
  9. data/Gemfile.lock +55 -0
  10. data/README.md +96 -0
  11. data/aggkit.gemspec +38 -0
  12. data/bin/agg +167 -0
  13. data/bin/aggconsul +222 -0
  14. data/bin/agglock +71 -0
  15. data/bin/aggmerge +118 -0
  16. data/bin/aggwait +262 -0
  17. data/bin/consul.rb +222 -0
  18. data/bin/locker.rb +71 -0
  19. data/bin/merger.rb +118 -0
  20. data/bin/terminator.rb +71 -0
  21. data/bin/waiter.rb +262 -0
  22. data/docker/Dockerfile +112 -0
  23. data/docker/docker-compose.yml +12 -0
  24. data/docker/down.sh +4 -0
  25. data/docker/run_tests.sh +23 -0
  26. data/lib/aggkit/childprocess/abstract_io.rb +38 -0
  27. data/lib/aggkit/childprocess/abstract_process.rb +194 -0
  28. data/lib/aggkit/childprocess/errors.rb +28 -0
  29. data/lib/aggkit/childprocess/jruby/io.rb +17 -0
  30. data/lib/aggkit/childprocess/jruby/process.rb +161 -0
  31. data/lib/aggkit/childprocess/jruby/pump.rb +55 -0
  32. data/lib/aggkit/childprocess/jruby.rb +58 -0
  33. data/lib/aggkit/childprocess/tools/generator.rb +148 -0
  34. data/lib/aggkit/childprocess/unix/fork_exec_process.rb +72 -0
  35. data/lib/aggkit/childprocess/unix/io.rb +22 -0
  36. data/lib/aggkit/childprocess/unix/lib.rb +188 -0
  37. data/lib/aggkit/childprocess/unix/platform/i386-linux.rb +14 -0
  38. data/lib/aggkit/childprocess/unix/platform/i386-solaris.rb +13 -0
  39. data/lib/aggkit/childprocess/unix/platform/x86_64-linux.rb +14 -0
  40. data/lib/aggkit/childprocess/unix/platform/x86_64-macosx.rb +13 -0
  41. data/lib/aggkit/childprocess/unix/posix_spawn_process.rb +135 -0
  42. data/lib/aggkit/childprocess/unix/process.rb +91 -0
  43. data/lib/aggkit/childprocess/unix.rb +11 -0
  44. data/lib/aggkit/childprocess/version.rb +5 -0
  45. data/lib/aggkit/childprocess/windows/handle.rb +93 -0
  46. data/lib/aggkit/childprocess/windows/io.rb +25 -0
  47. data/lib/aggkit/childprocess/windows/lib.rb +418 -0
  48. data/lib/aggkit/childprocess/windows/process.rb +132 -0
  49. data/lib/aggkit/childprocess/windows/process_builder.rb +177 -0
  50. data/lib/aggkit/childprocess/windows/structs.rb +151 -0
  51. data/lib/aggkit/childprocess/windows.rb +35 -0
  52. data/lib/aggkit/childprocess.rb +213 -0
  53. data/lib/aggkit/env.rb +219 -0
  54. data/lib/aggkit/runner.rb +80 -0
  55. data/lib/aggkit/version.rb +5 -0
  56. data/lib/aggkit/watcher.rb +239 -0
  57. data/lib/aggkit.rb +15 -0
  58. metadata +196 -0
@@ -0,0 +1,14 @@
1
+ module Aggkit
2
+ module ChildProcess::Unix::Platform
3
+ SIZEOF = {
4
+ :posix_spawn_file_actions_t => 76,
5
+ :posix_spawnattr_t => 336,
6
+ :sigset_t => 128
7
+ }
8
+ POSIX_SPAWN_RESETIDS = 1
9
+ POSIX_SPAWN_SETPGROUP = 2
10
+ POSIX_SPAWN_SETSIGDEF = 4
11
+ POSIX_SPAWN_SETSIGMASK = 8
12
+ POSIX_SPAWN_USEVFORK = 64
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ module Aggkit
2
+ module ChildProcess::Unix::Platform
3
+ SIZEOF = {
4
+ :posix_spawn_file_actions_t => 4,
5
+ :posix_spawnattr_t => 4,
6
+ :sigset_t => 16
7
+ }
8
+ POSIX_SPAWN_RESETIDS = 1
9
+ POSIX_SPAWN_SETPGROUP = 2
10
+ POSIX_SPAWN_SETSIGDEF = 4
11
+ POSIX_SPAWN_SETSIGMASK = 8
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ module Aggkit
2
+ module ChildProcess::Unix::Platform
3
+ SIZEOF = {
4
+ :posix_spawn_file_actions_t => 80,
5
+ :posix_spawnattr_t => 336,
6
+ :sigset_t => 128
7
+ }
8
+ POSIX_SPAWN_RESETIDS = 1
9
+ POSIX_SPAWN_SETPGROUP = 2
10
+ POSIX_SPAWN_SETSIGDEF = 4
11
+ POSIX_SPAWN_SETSIGMASK = 8
12
+ POSIX_SPAWN_USEVFORK = 64
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ module Aggkit
2
+ module ChildProcess::Unix::Platform
3
+ SIZEOF = {
4
+ :posix_spawn_file_actions_t => 8,
5
+ :posix_spawnattr_t => 8,
6
+ :sigset_t => 4
7
+ }
8
+ POSIX_SPAWN_RESETIDS = 1
9
+ POSIX_SPAWN_SETPGROUP = 2
10
+ POSIX_SPAWN_SETSIGDEF = 4
11
+ POSIX_SPAWN_SETSIGMASK = 8
12
+ end
13
+ end
@@ -0,0 +1,135 @@
1
+ require 'thread'
2
+
3
+ module Aggkit
4
+ module ChildProcess
5
+ module Unix
6
+ class PosixSpawnProcess < Process
7
+ private
8
+
9
+ @@cwd_lock = Mutex.new
10
+
11
+ def launch_process
12
+ pid_ptr = FFI::MemoryPointer.new(:pid_t)
13
+ actions = Lib::FileActions.new
14
+ attrs = Lib::Attrs.new
15
+
16
+ if io.stdout
17
+ actions.add_dup fileno_for(io.stdout), fileno_for(STDOUT)
18
+ else
19
+ actions.add_open fileno_for(STDOUT), "/dev/null", File::WRONLY, 0644
20
+ end
21
+
22
+ if io.stderr
23
+ actions.add_dup fileno_for(io.stderr), fileno_for(STDERR)
24
+ else
25
+ actions.add_open fileno_for(STDERR), "/dev/null", File::WRONLY, 0644
26
+ end
27
+
28
+ if duplex?
29
+ reader, writer = ::IO.pipe
30
+ actions.add_dup fileno_for(reader), fileno_for(STDIN)
31
+ actions.add_close fileno_for(writer)
32
+ end
33
+
34
+ attrs.pgroup = 0 if leader?
35
+ attrs.flags |= Platform::POSIX_SPAWN_USEVFORK if defined? Platform::POSIX_SPAWN_USEVFORK
36
+
37
+ # wrap in helper classes in order to avoid GC'ed pointers
38
+ argv = Argv.new(@args)
39
+ envp = Envp.new(ENV.to_hash.merge(@environment))
40
+
41
+ ret = 0
42
+ @@cwd_lock.synchronize do
43
+ Dir.chdir(@cwd || Dir.pwd) do
44
+ if ChildProcess.jruby?
45
+ # on JRuby, the current working directory is for some reason not inherited.
46
+ # We'll work around it by making a chdir call through FFI.
47
+ # TODO: report this to JRuby
48
+ Lib.chdir Dir.pwd
49
+ end
50
+
51
+ ret = Lib.posix_spawnp(
52
+ pid_ptr,
53
+ @args.first, # TODO: not sure this matches exec() behaviour
54
+ actions,
55
+ attrs,
56
+ argv,
57
+ envp
58
+ )
59
+ end
60
+ end
61
+
62
+ if duplex?
63
+ io._stdin = writer
64
+ reader.close
65
+ end
66
+
67
+ actions.free
68
+ attrs.free
69
+
70
+ if ret != 0
71
+ raise LaunchError, "#{Lib.strerror(ret)} (#{ret})"
72
+ end
73
+
74
+ @pid = pid_ptr.read_int
75
+ ::Process.detach(@pid) if detach?
76
+ end
77
+
78
+ if ChildProcess.jruby?
79
+ def fileno_for(obj)
80
+ ChildProcess::JRuby.posix_fileno_for(obj)
81
+ end
82
+ else
83
+ def fileno_for(obj)
84
+ obj.fileno
85
+ end
86
+ end
87
+
88
+ class Argv
89
+ def initialize(args)
90
+ @ptrs = args.map do |e|
91
+ if e.include?("\0")
92
+ raise ArgumentError, "argument cannot contain null bytes: #{e.inspect}"
93
+ end
94
+
95
+ FFI::MemoryPointer.from_string(e.to_s)
96
+ end
97
+
98
+ @ptrs << FFI::Pointer.new(0)
99
+ end
100
+
101
+ def to_ptr
102
+ argv = FFI::MemoryPointer.new(:pointer, @ptrs.size)
103
+ argv.put_array_of_pointer(0, @ptrs)
104
+
105
+ argv
106
+ end
107
+ end # Argv
108
+
109
+ class Envp
110
+ def initialize(env)
111
+ @ptrs = env.map do |key, val|
112
+ next if val.nil?
113
+
114
+ if key =~ /=|\0/ || val.include?("\0")
115
+ raise InvalidEnvironmentVariable, "#{key.inspect} => #{val.inspect}"
116
+ end
117
+
118
+ FFI::MemoryPointer.from_string("#{key}=#{val}")
119
+ end.compact
120
+
121
+ @ptrs << FFI::Pointer.new(0)
122
+ end
123
+
124
+ def to_ptr
125
+ env = FFI::MemoryPointer.new(:pointer, @ptrs.size)
126
+ env.put_array_of_pointer(0, @ptrs)
127
+
128
+ env
129
+ end
130
+ end # Envp
131
+
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,91 @@
1
+ module Aggkit
2
+ module ChildProcess
3
+ module Unix
4
+ class Process < AbstractProcess
5
+ attr_reader :pid
6
+
7
+ def io
8
+ @io ||= Unix::IO.new
9
+ end
10
+
11
+ def stop(timeout = 3)
12
+ assert_started
13
+ send_term
14
+
15
+ begin
16
+ return poll_for_exit(timeout)
17
+ rescue TimeoutError
18
+ # try next
19
+ end
20
+
21
+ send_kill
22
+ wait
23
+ rescue Errno::ECHILD, Errno::ESRCH
24
+ # handle race condition where process dies between timeout
25
+ # and send_kill
26
+ true
27
+ end
28
+
29
+ def exited?
30
+ return true if @exit_code
31
+
32
+ assert_started
33
+ pid, status = ::Process.waitpid2(_pid, ::Process::WNOHANG | ::Process::WUNTRACED)
34
+ pid = nil if pid == 0 # may happen on jruby
35
+
36
+ log(:pid => pid, :status => status)
37
+
38
+ if pid
39
+ set_exit_code(status)
40
+ end
41
+
42
+ !!pid
43
+ rescue Errno::ECHILD
44
+ # may be thrown for detached processes
45
+ true
46
+ end
47
+
48
+ def wait
49
+ assert_started
50
+
51
+ if exited?
52
+ exit_code
53
+ else
54
+ _, status = ::Process.waitpid2 _pid
55
+ set_exit_code(status)
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def send_term
62
+ send_signal 'TERM'
63
+ end
64
+
65
+ def send_kill
66
+ send_signal 'KILL'
67
+ end
68
+
69
+ def send_signal(sig)
70
+ assert_started
71
+
72
+ log "sending #{sig}"
73
+ ::Process.kill sig, _pid
74
+ end
75
+
76
+ def set_exit_code(status)
77
+ @exit_code = status.exitstatus || status.termsig
78
+ end
79
+
80
+ def _pid
81
+ if leader?
82
+ -@pid # negative pid == process group
83
+ else
84
+ @pid
85
+ end
86
+ end
87
+
88
+ end # Process
89
+ end # Unix
90
+ end # ChildProcess
91
+ end
@@ -0,0 +1,11 @@
1
+ module Aggkit
2
+ module ChildProcess
3
+ module Unix
4
+ end
5
+ end
6
+ end
7
+
8
+ require "aggkit/childprocess/unix/io"
9
+ require "aggkit/childprocess/unix/process"
10
+ require "aggkit/childprocess/unix/fork_exec_process"
11
+ # PosixSpawnProcess + ffi is required on demand.
@@ -0,0 +1,5 @@
1
+ module Aggkit
2
+ module ChildProcess
3
+ VERSION = '0.9.0'
4
+ end
5
+ end
@@ -0,0 +1,93 @@
1
+ module Aggkit
2
+ module ChildProcess
3
+ module Windows
4
+ class Handle
5
+
6
+ class << self
7
+ private :new
8
+
9
+ def open(pid, access = PROCESS_ALL_ACCESS)
10
+ handle = Lib.open_process(access, false, pid)
11
+
12
+ if handle.null?
13
+ raise Error, Lib.last_error_message
14
+ end
15
+
16
+ h = new(handle, pid)
17
+ return h unless block_given?
18
+
19
+ begin
20
+ yield h
21
+ ensure
22
+ h.close
23
+ end
24
+ end
25
+ end
26
+
27
+ attr_reader :pointer
28
+
29
+ def initialize(pointer, pid)
30
+ unless pointer.kind_of?(FFI::Pointer)
31
+ raise TypeError, "invalid handle: #{pointer.inspect}"
32
+ end
33
+
34
+ if pointer.null?
35
+ raise ArgumentError, "handle is null: #{pointer.inspect}"
36
+ end
37
+
38
+ @pid = pid
39
+ @pointer = pointer
40
+ @closed = false
41
+ end
42
+
43
+ def exit_code
44
+ code_pointer = FFI::MemoryPointer.new :ulong
45
+ ok = Lib.get_exit_code(@pointer, code_pointer)
46
+
47
+ if ok
48
+ code_pointer.get_ulong(0)
49
+ else
50
+ close
51
+ raise Error, Lib.last_error_message
52
+ end
53
+ end
54
+
55
+ def send(signal)
56
+ case signal
57
+ when 0
58
+ exit_code == PROCESS_STILL_ALIVE
59
+ when WIN_SIGINT
60
+ Lib.generate_console_ctrl_event(CTRL_C_EVENT, @pid)
61
+ when WIN_SIGBREAK
62
+ Lib.generate_console_ctrl_event(CTRL_BREAK_EVENT, @pid)
63
+ when WIN_SIGKILL
64
+ ok = Lib.terminate_process(@pointer, @pid)
65
+ Lib.check_error ok
66
+ else
67
+ thread_id = FFI::MemoryPointer.new(:ulong)
68
+ module_handle = Lib.get_module_handle("kernel32")
69
+ proc_address = Lib.get_proc_address(module_handle, "ExitProcess")
70
+
71
+ thread = Lib.create_remote_thread(@pointer, 0, 0, proc_address, 0, 0, thread_id)
72
+ check_error thread
73
+
74
+ Lib.wait_for_single_object(thread, 5)
75
+ true
76
+ end
77
+ end
78
+
79
+ def close
80
+ return if @closed
81
+
82
+ Lib.close_handle(@pointer)
83
+ @closed = true
84
+ end
85
+
86
+ def wait(milliseconds = nil)
87
+ Lib.wait_for_single_object(@pointer, milliseconds || INFINITE)
88
+ end
89
+
90
+ end # Handle
91
+ end # Windows
92
+ end # ChildProcess
93
+ end
@@ -0,0 +1,25 @@
1
+ module Aggkit
2
+ module ChildProcess
3
+ module Windows
4
+ class IO < AbstractIO
5
+ private
6
+
7
+ def check_type(io)
8
+ return if has_fileno?(io)
9
+ return if has_to_io?(io)
10
+
11
+ raise ArgumentError, "#{io.inspect}:#{io.class} must have :fileno or :to_io"
12
+ end
13
+
14
+ def has_fileno?(io)
15
+ io.respond_to?(:fileno) && io.fileno
16
+ end
17
+
18
+ def has_to_io?(io)
19
+ io.respond_to?(:to_io) && io.to_io.kind_of?(::IO)
20
+ end
21
+
22
+ end # IO
23
+ end # Windows
24
+ end # ChildProcess
25
+ end