bandicoot 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in bandicoot.gemspec
4
+ gemspec
@@ -0,0 +1,87 @@
1
+ Bandicoot
2
+ =========
3
+
4
+ "I get knocked down, but I get up again. You're never gonna keep me down" --
5
+ Chumbawumba
6
+
7
+ Wouldn't video games suck without levels and save points? I can't even tell
8
+ you the number of times I've been playing Pokemon, forgetting to save
9
+ regularly, only to get in a trainer battle right when my mom calls me down to
10
+ dinner. I was angry for the rest of the day thinking about how all that
11
+ effort getting the Magikarp enough XP to evolve was wasted in that one flick
12
+ of the power switch.
13
+
14
+ I feel similarly about my long running, data intensive tasks in Ruby. If I'm
15
+ loading in a file with 200,000 records, some jackass has probably put a
16
+ Windows-1252 character in record 195,095 or so. Restarting this process from
17
+ the beginning would throw me into a rage.
18
+
19
+ Bandicoot lets you set save points from which future computation can resume.
20
+ Have a boss battle with some complicated data 5 hours into a process? No
21
+ problem! Bandicoot will let you try and fix the bugs and start again.
22
+
23
+ Warning
24
+ -------
25
+
26
+ Bandicoot is a work in progress. Bandicoot is not currently thread (or Fiber)
27
+ safe. I'm working on a way to make it work while maintaining decent
28
+ semantics, but it is not there yet. It may destroy your computer and force
29
+ you to lightly blow on your cartridges.
30
+
31
+ Usage
32
+ -----
33
+
34
+ Basic usage involves setting Bandicoot up and defining save_points
35
+
36
+ Bandicoot.start do
37
+ 5.times do |i|
38
+ the_meaning_of_life = 0
39
+ the_meaning_of_life += Bandicoot.save_point(["outer", i]) do
40
+ result = 0
41
+ 10.times do |j|
42
+ result += Bandicoot.save_point(["inner", j]) do
43
+ expensive_computation(j)
44
+ end
45
+ end
46
+ result
47
+ end
48
+ the_meaning_of_life
49
+ end
50
+ end
51
+
52
+ Save points have a key and take a block. The return value of
53
+ Bandicoot.save_point is the return of the block. Save points can be nested
54
+ arbitrarily and proper scoping will be maintained.
55
+
56
+ This example will always run all of the code, writing its progress out to a
57
+ save file. If the program crashes, you can continue from where it left off by
58
+ changing the first line to
59
+
60
+ Bandicoot.start(:continue => "path_to_save_file.save") do
61
+
62
+ It will then load in that file and whenever a save_point is encountered, it
63
+ will check whether that save_point was completed. If it has been, the return
64
+ value it gave last time will be returned and the block will not be run. If it
65
+ has not been, it will be run and upon successful completion that save point
66
+ will be recorded as well. In this manner, you can keep trying until you get
67
+ it right.
68
+
69
+ Caveats and Considerations
70
+ --------------------------
71
+
72
+ 1) Bandicoot uses msgpack for serialization of keys and return values;
73
+ therefore, keys and return values must be primitives where item ==
74
+ deserialize(serialize(item)). Practically, this means primitives and NO
75
+ SYMBOLS. Arbitrarily nested arrays/hashes, numbers, and strings will work
76
+ fine.
77
+
78
+ 2) The whole point of Bandicoot is to skip over already run blocks, so
79
+ obviously any side effects in that block are not guaranteed to occur.
80
+ Oftentimes this is fine and desired (e.g. inserting a row into a database),
81
+ but if later code depends on side effects from earlier code, you may have a
82
+ bad day.
83
+
84
+ 3) Your code could fail at any point in the save point block. Bandicoot will
85
+ rerun the entire failing block, a bit of idempotence would probably be a good
86
+ idea.
87
+
@@ -0,0 +1,10 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake/testtask'
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << 'lib' << 'test'
7
+ test.pattern = 'test/**/*_test.rb'
8
+ test.verbose = true
9
+ end
10
+
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "bandicoot/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "bandicoot"
7
+ s.version = Bandicoot::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Ben Hughes"]
10
+ s.email = ["ben@nabewise.com"]
11
+ s.homepage = ""
12
+ s.summary = %q{Easy resume/save point lib}
13
+ s.description = %q{Doesn't it suck when a long running task crashes? Wouldn't it be great to resume from more or less where you left off.}
14
+
15
+ s.rubyforge_project = "bandicoot"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency "msgpack"
23
+
24
+ s.add_development_dependency "mocha"
25
+ s.add_development_dependency "fakefs"
26
+ end
@@ -0,0 +1,43 @@
1
+ require 'bandicoot/save_file'
2
+ require 'bandicoot/save_point_hash'
3
+ require 'bandicoot/context'
4
+
5
+ module Bandicoot
6
+ @@current = nil
7
+
8
+ def self.start(opts={})
9
+ raise AlreadyStartedError if Bandicoot.current
10
+ Bandicoot.push_context(opts)
11
+ begin
12
+ yield
13
+ ensure
14
+ Bandicoot.current.save_file.close
15
+ Bandicoot.pop_context
16
+ end
17
+ end
18
+
19
+ def self.current
20
+ @@current
21
+ end
22
+
23
+ def self.save_point(key, &blk)
24
+ raise NotStartedError unless Bandicoot.current
25
+ Bandicoot.push_context(:key => key)
26
+ begin
27
+ Bandicoot.current.run(blk)
28
+ ensure
29
+ Bandicoot.pop_context
30
+ end
31
+ end
32
+
33
+ def self.push_context(opts={})
34
+ @@current = Bandicoot::Context.new(opts.merge(:parent => Bandicoot.current))
35
+ end
36
+
37
+ def self.pop_context
38
+ @@current = Bandicoot.current.parent
39
+ end
40
+
41
+ class AlreadyStartedError < RuntimeError; end
42
+ class NotStartedError < RuntimeError; end
43
+ end
@@ -0,0 +1,60 @@
1
+ module Bandicoot
2
+ class Context
3
+ attr_reader :parent, :save_points
4
+
5
+ def initialize(opts={})
6
+ @parent = opts[:parent]
7
+ @key = opts[:key]
8
+
9
+ # if this is the top level context
10
+ if !parent
11
+ @key ||= "__main__"
12
+ @continuing = !!opts[:continue]
13
+ if continuing?
14
+ @save_file = SaveFile.continue(opts[:continue])
15
+ @save_points = @save_file.save_points[@key] || SavePointHash.new
16
+ else
17
+ @save_file = SaveFile.create(opts[:save_file] || default_save_filename)
18
+ @save_points = SavePointHash.new
19
+ end
20
+ else
21
+ @save_points = parent.save_points[@key] || SavePointHash.new
22
+ end
23
+ end
24
+
25
+ def key
26
+ @m_key ||= ((parent && parent.key) || []) + [@key]
27
+ end
28
+
29
+ def save_file
30
+ @save_file ||= (parent && parent.save_file)
31
+ end
32
+
33
+ def continuing?
34
+ @continuing ||= (parent && parent.continuing?)
35
+ end
36
+
37
+ # makes things a little prettier
38
+ def save_point
39
+ @save_points
40
+ end
41
+
42
+ def run(blk)
43
+ if continuing? && save_point.completed?
44
+ save_point.ret_val
45
+ else
46
+ finish! blk.call
47
+ end
48
+ end
49
+
50
+ def finish!(retval=nil)
51
+ save_file.write(key, retval) if save_file
52
+ retval
53
+ end
54
+
55
+ protected
56
+ def default_save_filename
57
+ "bandicoot-#{Time.now.to_i}-#{rand(65535)}.save"
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,47 @@
1
+ require 'msgpack'
2
+
3
+ module Bandicoot
4
+ class SaveFile
5
+ attr_reader :file, :save_points
6
+
7
+ def self.create(filename)
8
+ new(File.open(filename, "w"))
9
+ end
10
+
11
+ def self.continue(filename)
12
+ file = File.open(filename, "r+")
13
+ save_points = read_save_points(file)
14
+ file.seek(0, IO::SEEK_END)
15
+ new(file, save_points)
16
+ end
17
+
18
+ def initialize(file, save_points=nil)
19
+ @file = file
20
+ @save_points = save_points
21
+ end
22
+
23
+ def write(key, retval)
24
+ file.write([key, retval].to_msgpack)
25
+ end
26
+
27
+ def close
28
+ file.close
29
+ end
30
+
31
+ protected
32
+ def self.read_save_points(file)
33
+ hash = SavePointHash.new
34
+ MessagePack::Unpacker.new(file).each do |key, retval|
35
+ c = hash
36
+ key.each do |x|
37
+ c[x] ||= SavePointHash.new
38
+ c = c[x]
39
+ end
40
+ c.complete = true
41
+ c.ret_val = retval
42
+ end
43
+ p hash
44
+ hash
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,9 @@
1
+ module Bandicoot
2
+ class SavePointHash < Hash
3
+ attr_accessor :ret_val, :complete
4
+
5
+ def completed?
6
+ !!complete
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module Bandicoot
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,57 @@
1
+ require File.join(File.dirname(__FILE__), "helper")
2
+
3
+ class BandicootTest < Test::Unit::TestCase
4
+
5
+ def teardown
6
+ FakeFS::FileSystem.clear
7
+ end
8
+
9
+ def test_start
10
+ started = false
11
+ Bandicoot.start do
12
+ started = true
13
+ assert Bandicoot.current
14
+ end
15
+ assert started
16
+ end
17
+
18
+ def test_start_with_custom_path
19
+ Bandicoot.start(:save_file => "blah") do
20
+ 1+1
21
+ end
22
+ assert File.exists?("blah")
23
+ end
24
+
25
+ def test_start_with_continue
26
+ File.open("blah", "w").close
27
+ Bandicoot.start(:continue => false) do
28
+ assert !Bandicoot.current.continuing?
29
+ end
30
+
31
+ Bandicoot.start(:continue => "blah") do
32
+ assert Bandicoot.current.continuing?
33
+ end
34
+ end
35
+
36
+ def test_save_point
37
+ run_count = 0
38
+ Bandicoot.start(:save_file => "blah") do
39
+ x = Bandicoot.save_point(:incr) do
40
+ run_count += 1
41
+ 42
42
+ end
43
+ assert_equal 42, x
44
+ end
45
+
46
+ Bandicoot.start(:continue => "blah") do
47
+ x = Bandicoot.save_point("incr") do
48
+ run_count += 1
49
+ 42
50
+ end
51
+ assert_equal 42, x
52
+ end
53
+
54
+ assert_equal 1, run_count
55
+ end
56
+ end
57
+
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'redgreen'
3
+ require 'bundler/setup'
4
+
5
+ require 'bandicoot'
6
+
7
+ require 'test/unit'
8
+ require 'fakefs'
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bandicoot
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Ben Hughes
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-07-15 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: msgpack
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: mocha
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: fakefs
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ type: :development
62
+ version_requirements: *id003
63
+ description: Doesn't it suck when a long running task crashes? Wouldn't it be great to resume from more or less where you left off.
64
+ email:
65
+ - ben@nabewise.com
66
+ executables: []
67
+
68
+ extensions: []
69
+
70
+ extra_rdoc_files: []
71
+
72
+ files:
73
+ - .gitignore
74
+ - Gemfile
75
+ - README.md
76
+ - Rakefile
77
+ - bandicoot.gemspec
78
+ - lib/bandicoot.rb
79
+ - lib/bandicoot/context.rb
80
+ - lib/bandicoot/save_file.rb
81
+ - lib/bandicoot/save_point_hash.rb
82
+ - lib/bandicoot/version.rb
83
+ - test/bandicoot_test.rb
84
+ - test/helper.rb
85
+ has_rdoc: true
86
+ homepage: ""
87
+ licenses: []
88
+
89
+ post_install_message:
90
+ rdoc_options: []
91
+
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ hash: 3
100
+ segments:
101
+ - 0
102
+ version: "0"
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ hash: 3
109
+ segments:
110
+ - 0
111
+ version: "0"
112
+ requirements: []
113
+
114
+ rubyforge_project: bandicoot
115
+ rubygems_version: 1.4.1
116
+ signing_key:
117
+ specification_version: 3
118
+ summary: Easy resume/save point lib
119
+ test_files:
120
+ - test/bandicoot_test.rb
121
+ - test/helper.rb