iso_latte 1.0
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.
- checksums.yaml +7 -0
- data/lib/iso_latte/version.rb +3 -0
- data/lib/iso_latte.rb +92 -0
- metadata +47 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5780f40ea4f18590135725740fccd26e885510fd
|
4
|
+
data.tar.gz: 3201f17dad3af7bf0be4ae3b42596601f7c9b62a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 86beb89d8fcfb691d81748deb32169fbf4aa41aa4c43c0cccfd49d901f25c5dfa2430588f44e2cc2986013aa73975a64ae1aec5104096ce98a2c7bde780ee91a
|
7
|
+
data.tar.gz: fe992f6f80a746bcdbd5287d77c760498a221a8c5be79d8cdcf8ad960b9f9d957f4a7a16e78ccbb39c3f1fc72eaec714557e73800f11ef6d5eb62af724ee91b5
|
data/lib/iso_latte.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
require "ostruct"
|
2
|
+
|
3
|
+
module IsoLatte
|
4
|
+
NO_EXIT = 122
|
5
|
+
EXCEPTION_RAISED = 123
|
6
|
+
|
7
|
+
# Available options:
|
8
|
+
# stderr: a path to write stderr into. Defaults to '/dev/null'
|
9
|
+
# nil means "do not change stderr"
|
10
|
+
# finish: a callable to execute when the subprocess terminates (in any way).
|
11
|
+
# receives a boolean 'success' value, and an exitstatus as arguments
|
12
|
+
# success: a callable to execute if the subprocess completes successfully.
|
13
|
+
# note: this can exit ALONGSIDE the exit callback, if the subprocess
|
14
|
+
# exits with zero explicitly!
|
15
|
+
# kill: a callable to execute if the subprocess is killed (SIGKILL).
|
16
|
+
# fault: a callable to execute if the subprocess segfaults, core dumps, etc.
|
17
|
+
# exit: a callable to execute if the subprocess voluntarily exits with nonzero.
|
18
|
+
# receives the exit status value as its argument.
|
19
|
+
#
|
20
|
+
# It is allowable to Isolatte.fork from inside an IsoLatte.fork block (reentrant)
|
21
|
+
#
|
22
|
+
# We are using the exit statuses of 122 and 123 as sentinels that mean
|
23
|
+
# 'the code did not exit on its own' and 'the code raised an exception'.
|
24
|
+
# If you have code that actually uses those exit statuses.. change the special
|
25
|
+
# statuses I guess.
|
26
|
+
#
|
27
|
+
def self.fork(options = nil, &block)
|
28
|
+
defaults = { stderr: "/dev/null", exit: nil }
|
29
|
+
opts = OpenStruct.new(defaults.merge(options || {}))
|
30
|
+
|
31
|
+
read_ex, write_ex = IO.pipe
|
32
|
+
|
33
|
+
child_pid = Process.fork do
|
34
|
+
read_ex.close
|
35
|
+
begin
|
36
|
+
if opts.stderr
|
37
|
+
File.open(opts.stderr, "w") do |stderr_file|
|
38
|
+
STDERR.reopen(stderr_file)
|
39
|
+
STDERR.sync = true
|
40
|
+
$stderr = STDERR
|
41
|
+
block.call
|
42
|
+
end
|
43
|
+
else
|
44
|
+
block.call
|
45
|
+
end
|
46
|
+
rescue StandardError => e
|
47
|
+
# Things that stick un-marshalable attributes on exceptions need to be handled here.
|
48
|
+
ivars = ["@__better_errors_bindings_stack"]
|
49
|
+
ivars.each { |v| e.instance_variable_set(v, nil) if e.instance_variable_defined?(v) }
|
50
|
+
|
51
|
+
Marshal.dump e, write_ex
|
52
|
+
write_ex.flush
|
53
|
+
write_ex.close
|
54
|
+
exit!(EXCEPTION_RAISED)
|
55
|
+
end
|
56
|
+
|
57
|
+
exit!(NO_EXIT)
|
58
|
+
end
|
59
|
+
|
60
|
+
write_ex.close
|
61
|
+
|
62
|
+
pid, rc = Process.wait2(child_pid)
|
63
|
+
fail(Error, "Wrong child's exit received!") unless pid == child_pid
|
64
|
+
|
65
|
+
if rc.exited? && rc.exitstatus == EXCEPTION_RAISED
|
66
|
+
e = Marshal.load read_ex
|
67
|
+
read_ex.close
|
68
|
+
fail e
|
69
|
+
else
|
70
|
+
read_ex.close
|
71
|
+
end
|
72
|
+
|
73
|
+
success = rc.success? || rc.exitstatus == NO_EXIT
|
74
|
+
code = rc.exitstatus == NO_EXIT ? 0 : rc.exitstatus
|
75
|
+
|
76
|
+
if success
|
77
|
+
opts.success.call if opts.success
|
78
|
+
else
|
79
|
+
opts.fault.call if opts.fault && (rc.termsig == 6 || rc.coredump?)
|
80
|
+
opts.kill.call if opts.kill && rc.termsig == 9
|
81
|
+
end
|
82
|
+
|
83
|
+
# This can execute on success OR failure - it indicates that the subprocess
|
84
|
+
# *explicitly* exited, whether with zero or nonzero.
|
85
|
+
opts.exit.call(rc.exitstatus) if opts.exit && rc.exited? && rc.exitstatus != NO_EXIT
|
86
|
+
|
87
|
+
# This should execute *no matter what*
|
88
|
+
opts.finish.call(success, code) if opts.finish
|
89
|
+
end
|
90
|
+
|
91
|
+
class Error < StandardError; end
|
92
|
+
end
|
metadata
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: iso_latte
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '1.0'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Emcien Engineering
|
8
|
+
- Eric Mueller
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2015-07-01 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: IsoLatte allows execution to be forked from the main process for safety
|
15
|
+
email:
|
16
|
+
- engineering@emcien.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- lib/iso_latte.rb
|
22
|
+
- lib/iso_latte/version.rb
|
23
|
+
homepage: https://github.com/emcien/iso_latte
|
24
|
+
licenses:
|
25
|
+
- BSD-3-Clause
|
26
|
+
metadata: {}
|
27
|
+
post_install_message:
|
28
|
+
rdoc_options: []
|
29
|
+
require_paths:
|
30
|
+
- lib
|
31
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '0'
|
36
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
requirements: []
|
42
|
+
rubyforge_project:
|
43
|
+
rubygems_version: 2.4.3
|
44
|
+
signing_key:
|
45
|
+
specification_version: 4
|
46
|
+
summary: A gem for isolating execution in a subprocess
|
47
|
+
test_files: []
|