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