pry-timetravel 0.0.1 → 0.0.2
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 +4 -4
- data/LICENSE.MIT +1 -1
- data/README.md +18 -6
- data/Rakefile +2 -2
- data/lib/pry-timetravel.rb +89 -60
- data/lib/pry-timetravel/commands.rb +37 -29
- data/pry-timetravel.gemspec +1 -1
- metadata +2 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5d28f753687e1f5f96c87e7ce51949dc68d8fab6
|
4
|
+
data.tar.gz: be676c5cc7e8ff236314b31e6161aef76ce017a4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c42224c2d9c89770cf5f22c861b37fa254a71713d11c46696fbc6d912027f21454a0ea98688f13f690a69e7fc7a5e12b8a1c63e2b2d61603c7c192bbebfb0c01
|
7
|
+
data.tar.gz: 27bfcc69e86ff9838820f350c5fb617e24d6d1c456e702b670c8c25d7156b9afdb5be0c728b9416f8645696759c4482930e3f0f9e309344635fc9638b2636fed
|
data/LICENSE.MIT
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c)
|
1
|
+
Copyright (c) 2014 Brock Wilcox <awwaiid@thelackthereof.org>
|
2
2
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
4
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
@@ -1,21 +1,33 @@
|
|
1
1
|
# pry-timetravel
|
2
2
|
|
3
|
-
DOES NOT WORK. DO NOT USE.
|
4
|
-
|
5
3
|
Time travel! For your REPL!
|
6
4
|
|
7
5
|
> x = 5
|
8
6
|
5
|
9
|
-
>
|
7
|
+
> snap
|
10
8
|
> x = 10
|
11
9
|
10
|
12
|
-
>
|
10
|
+
> back
|
13
11
|
> x
|
14
12
|
5
|
15
13
|
|
16
14
|
This is an attempt to package the proof of concept. API WILL CHANGE!
|
17
15
|
|
16
|
+
## How it works
|
17
|
+
|
18
|
+
The 'snap' command causes a fork, and the child is sent a SIGSTOP to freeze the
|
19
|
+
process. The parent continues on. Later when you do 'back' the saved child is
|
20
|
+
sent SIGCONT and the current process is sent a SIGSTOP. Ultimately you can have
|
21
|
+
a whole pool of frozen snapshots and can resume any of them.
|
22
|
+
|
23
|
+
In theory copy-on-write semantics makes this a tollerable thing to do :)
|
24
|
+
|
25
|
+
There are a few more details, but that is the important bit.
|
26
|
+
|
27
|
+
WARNING: Time travel may cause zombies.
|
28
|
+
|
18
29
|
## Meta
|
19
30
|
|
20
|
-
Released under the MIT license, see LICENSE.MIT for details.
|
21
|
-
are welcome
|
31
|
+
Released under the MIT license, see LICENSE.MIT for details. License is
|
32
|
+
negotiable. Contributions and bug-reports are welcome!
|
33
|
+
|
data/Rakefile
CHANGED
data/lib/pry-timetravel.rb
CHANGED
@@ -7,81 +7,110 @@ class PryTimetravel
|
|
7
7
|
class << self
|
8
8
|
|
9
9
|
def dlog(msg)
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
if ENV["TIMETRAVEL_DEBUG"]
|
11
|
+
File.open("meta.log", 'a') do |file|
|
12
|
+
file.puts("#{Time.now} [#{$$}] #{msg}")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
at_exit do
|
18
|
+
PryTimetravel.dlog "at_exit"
|
19
|
+
if $root_parent && $$ != $root_parent
|
20
|
+
PryTimetravel.dlog "Sending SIGUSR1 up to #{$root_parent}"
|
21
|
+
Process.kill 'SIGUSR1', $root_parent
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def enter_suspended_animation
|
26
|
+
old_sigcont_handler = Signal.trap('CONT') do
|
27
|
+
dlog("Got a SIGCONT")
|
28
|
+
end
|
29
|
+
|
30
|
+
old_sigexit_handler = Signal.trap('EXIT') do
|
31
|
+
dlog("got EXIT")
|
32
|
+
Kernel.exit!
|
33
|
+
end
|
34
|
+
|
35
|
+
dlog("Stopping myself")
|
36
|
+
Process.kill 'SIGSTOP', $$
|
37
|
+
dlog("Back from SIGSTOP!")
|
38
|
+
|
39
|
+
dlog("Returning to old SIGCONT")
|
40
|
+
Signal.trap('CONT', old_sigcont_handler)
|
41
|
+
dlog("Returning to old SIGEXIT")
|
42
|
+
Signal.trap('EXIT', old_sigexit_handler)
|
13
43
|
end
|
14
44
|
|
15
|
-
def
|
45
|
+
def snapshot
|
46
|
+
|
47
|
+
# We need a root-parent to keep the shell happy
|
48
|
+
if ! $root_parent
|
49
|
+
$root_parent = $$
|
50
|
+
child_pid = fork
|
51
|
+
if child_pid
|
52
|
+
Signal.trap('INT') do
|
53
|
+
dlog("root-parent got INT")
|
54
|
+
end
|
55
|
+
Signal.trap('USR1') do
|
56
|
+
dlog("root-parent got USR1")
|
57
|
+
Kernel.exit!
|
58
|
+
end
|
59
|
+
dlog "Root parent waiting on #{child_pid}"
|
60
|
+
Process.waitpid child_pid
|
61
|
+
dlog "Root parent exiting!"
|
62
|
+
Kernel.exit!
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
$timetravel_root ||= $$
|
67
|
+
|
16
68
|
parent_pid = $$
|
17
69
|
child_pid = fork
|
18
70
|
if child_pid
|
19
|
-
dlog("
|
20
|
-
|
21
|
-
# Method
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
#
|
26
|
-
|
27
|
-
# Problem: What if the child exits in a bad way? Check out '$?'
|
28
|
-
Process.waitpid(child_pid)
|
29
|
-
dlog("ME #{$$}: Previous exit: #{$?.inspect}")
|
30
|
-
if $?.exitstatus != 42
|
31
|
-
# Since this wasn't a timetravel return, we must unravel this world
|
32
|
-
Kernel.exit! $?.exitstatus
|
33
|
-
end
|
71
|
+
dlog("I am parent #{parent_pid}: I have a child pid #{child_pid}")
|
72
|
+
|
73
|
+
# Method 3: Child suspends themselves, parent adds them to list
|
74
|
+
@previous_pid ||= []
|
75
|
+
@previous_pid.push child_pid
|
76
|
+
|
77
|
+
# Perform operation
|
78
|
+
yield
|
34
79
|
|
35
|
-
# The parent universe freezes
|
36
|
-
# dlog("PARENT #{$$}: I am suspending. My child is #{parent_pid}")
|
37
|
-
# dlog("PARENT #{$$}: suspending")
|
38
|
-
# Process.setpgrp
|
39
|
-
# Process.setsid
|
40
|
-
# dlog("PARENT #{$$}: resumed!")
|
41
80
|
else
|
42
81
|
child_pid = $$
|
43
|
-
dlog("
|
82
|
+
dlog("I am child #{child_pid}: I have a parent pid #{parent_pid}")
|
83
|
+
enter_suspended_animation
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def snapshot_list
|
88
|
+
@previous_pid && @previous_pid.join(" ")
|
89
|
+
end
|
44
90
|
|
45
|
-
|
46
|
-
|
47
|
-
# Process.kill 'SIGSTOP', child_pid
|
48
|
-
# dlog("CHILD #{child_pid}: resumed!")
|
91
|
+
def restore_snapshot(target_pid = nil, count = nil)
|
92
|
+
dlog("Thinking about time travel...");
|
49
93
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
94
|
+
if target_pid.nil? && @previous_pid && ! @previous_pid.empty?
|
95
|
+
count = 1 if count == 0
|
96
|
+
target_pid = @previous_pid[-count]
|
97
|
+
else
|
98
|
+
target_pid = target_pid
|
99
|
+
end
|
55
100
|
|
101
|
+
if target_pid
|
102
|
+
dlog("ME #{$$}: I found a target pid #{target_pid}! TIME TRAVEL TIME")
|
103
|
+
Process.kill 'SIGCONT', target_pid
|
104
|
+
enter_suspended_animation
|
105
|
+
else
|
106
|
+
dlog("I was unable to time travel. Maybe it is a myth.");
|
56
107
|
end
|
57
108
|
end
|
58
109
|
|
59
|
-
def
|
60
|
-
|
61
|
-
dlog("ME #{$$}: previous_pid = #{ @previous_pid }");
|
62
|
-
if @previous_pid && ! @previous_pid.empty?
|
63
|
-
previous_pid = @previous_pid.pop
|
64
|
-
dlog("ME #{$$}: I found a previous pid #{previous_pid}! TIME TRAVEL TIME")
|
65
|
-
|
66
|
-
# Method 1: Awaken the child and let them take over
|
67
|
-
# Main parent can't exit or shell will get upset, so wait for all children
|
68
|
-
# Once all children are done, kill ourself
|
69
|
-
# Process.kill 'SIGCONT', previous_pid
|
70
|
-
# dlog("ME #{$$}: I resumed pid #{previous_pid}... now time to wait")
|
71
|
-
# # Process.waitpid(previous_pid)
|
72
|
-
# Process.waitall
|
73
|
-
# dlog("ME #{$$}: If you meet your previous self, kill yourself.")
|
74
|
-
# # Process.kill 'SIGKILL', $$
|
75
|
-
# Kernel.exit!
|
76
|
-
|
77
|
-
# Method 2: Kill ourself and let the parent take over
|
78
|
-
# The parent was just doing a waitpid, so it is ready
|
79
|
-
# Process.kill 'SIGKILL', $$
|
80
|
-
Process.exit! 42
|
81
|
-
|
82
|
-
end
|
83
|
-
dlog("ME #{$$}: I was unable to time travel. Maybe it is a myth.");
|
110
|
+
def restore_root_snapshot
|
111
|
+
restore_snapshot($timetravel_root) if $timetravel_root
|
84
112
|
end
|
113
|
+
|
85
114
|
end
|
86
115
|
end
|
87
116
|
|
@@ -1,49 +1,57 @@
|
|
1
1
|
|
2
|
-
Pry::Commands.create_command "
|
3
|
-
match '
|
2
|
+
Pry::Commands.create_command "snap", "Create a snapshot that you can later return to" do
|
3
|
+
match 'snap'
|
4
4
|
group 'Timetravel'
|
5
|
-
# description 'Snapshot the world so we can timetravel back here later'
|
6
5
|
banner <<-'BANNER'
|
7
|
-
Usage:
|
8
|
-
|
9
|
-
checkpoint --delete [INDEX]
|
6
|
+
Usage: snap [cmd]
|
7
|
+
snap --list
|
10
8
|
|
11
|
-
This will add a
|
9
|
+
This will add a snapshot which you can return to later.
|
10
|
+
|
11
|
+
If you provide [cmd] then that command will also be run -- nice for "snap next" in conjunction with pry-byebug.
|
12
12
|
BANNER
|
13
13
|
|
14
|
-
|
15
|
-
# opt.on :d, :delete
|
16
|
-
# "Delete the
|
14
|
+
def options(opt)
|
15
|
+
# opt.on :d, :delete=,
|
16
|
+
# "Delete the snapshot with the given index. If no index is given delete them all",
|
17
17
|
# :optional_argument => true, :as => Integer
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
opt.on :l, :list,
|
19
|
+
"Show a list of existing snapshots"
|
20
|
+
end
|
21
21
|
def process
|
22
|
-
|
22
|
+
if opts.l?
|
23
|
+
output.puts PryTimetravel.snapshot_list
|
24
|
+
else
|
25
|
+
PryTimetravel.snapshot do
|
26
|
+
run(args.join(" ")) unless args.empty?
|
27
|
+
end
|
28
|
+
end
|
23
29
|
end
|
24
30
|
end
|
25
31
|
|
26
|
-
Pry::Commands.create_command "
|
27
|
-
match '
|
32
|
+
Pry::Commands.create_command "back", "Go back to the most recent snapshot" do
|
33
|
+
match 'back'
|
28
34
|
group 'Timetravel'
|
29
|
-
# description 'Snapshot the world so we can timetravel back here later'
|
30
35
|
banner <<-'BANNER'
|
31
|
-
Usage:
|
32
|
-
|
33
|
-
|
36
|
+
Usage: back [count]
|
37
|
+
back --pid pid
|
38
|
+
back --home
|
34
39
|
|
35
|
-
|
40
|
+
Go back to a previous snapshot.
|
36
41
|
BANNER
|
37
42
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
# "Show all checkpoints"
|
44
|
-
# end
|
43
|
+
def options(opt)
|
44
|
+
opt.on :p, :pid=, "Jump (back) to a specific snapshot identified by [pid]",
|
45
|
+
:as => Integer
|
46
|
+
opt.on :home, "Jump to the end of the original execution sequence"
|
47
|
+
end
|
45
48
|
def process
|
46
|
-
|
49
|
+
if opts.h?
|
50
|
+
PryTimetravel.restore_root_snapshot
|
51
|
+
else
|
52
|
+
count = args.first.to_i
|
53
|
+
PryTimetravel.restore_snapshot(opts[:p], count)
|
54
|
+
end
|
47
55
|
end
|
48
56
|
end
|
49
57
|
|
data/pry-timetravel.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pry-timetravel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brock Wilcox
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-10-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pry
|
@@ -81,4 +81,3 @@ signing_key:
|
|
81
81
|
specification_version: 4
|
82
82
|
summary: Timetravel
|
83
83
|
test_files: []
|
84
|
-
has_rdoc:
|