fileq 0.1.3
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.
- data/README +15 -0
- data/lib/fileq.rb +455 -0
- data/lib/job.rb +181 -0
- data/lib/lockfile.rb +88 -0
- data/test/fileq_createdir_tests.rb +83 -0
- data/test/fileq_find_tests.rb +74 -0
- data/test/fileq_first_tests.rb +33 -0
- data/test/fileq_job_tests.rb +218 -0
- data/test/fileq_queue_tests.rb +365 -0
- data/test/fileq_test.rb +12 -0
- data/test/lockfile_test.rb +4 -0
- data/test/lockfile_tests.rb +132 -0
- data/test/test_helper.rb +8 -0
- metadata +67 -0
data/lib/job.rb
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
# FileQ
|
2
|
+
require 'yaml'
|
3
|
+
#require 'fcntl'
|
4
|
+
|
5
|
+
module Xxeo
|
6
|
+
|
7
|
+
class WrongStatus < StandardError; end
|
8
|
+
|
9
|
+
class Job
|
10
|
+
|
11
|
+
def Job.create(queue, name, path, status)
|
12
|
+
raise unless queue.class == Xxeo::FileQ
|
13
|
+
|
14
|
+
Job.new(queue, name, path, status)
|
15
|
+
end
|
16
|
+
|
17
|
+
def name
|
18
|
+
return @name
|
19
|
+
end
|
20
|
+
|
21
|
+
def data
|
22
|
+
raise WrongStatus.new('cannot disown a non-owned job') unless @own
|
23
|
+
data = nil
|
24
|
+
File.open(@path + '/data') { |f| data = f.read }
|
25
|
+
return data
|
26
|
+
end
|
27
|
+
|
28
|
+
def owning_pid
|
29
|
+
@owning_pid
|
30
|
+
end
|
31
|
+
|
32
|
+
def is_file?
|
33
|
+
meta = read_meta
|
34
|
+
raise unless meta
|
35
|
+
return meta[:XX_type] == 'file'
|
36
|
+
end
|
37
|
+
|
38
|
+
def original_pathname
|
39
|
+
meta = read_meta
|
40
|
+
raise unless meta
|
41
|
+
return meta[:XX_original_file_name] if meta[:XX_type] == 'file'
|
42
|
+
return nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def read_meta
|
46
|
+
return @fq.meta_for_job(@name)
|
47
|
+
end
|
48
|
+
|
49
|
+
def own?
|
50
|
+
@own
|
51
|
+
end
|
52
|
+
|
53
|
+
def disown
|
54
|
+
raise WrongStatus.new('cannot disown a non-owned job') unless @own
|
55
|
+
@own = false
|
56
|
+
File.unlink(@path + '/pid')
|
57
|
+
@owning_pid = nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def set_status(path, status)
|
61
|
+
@path = path
|
62
|
+
@status = status
|
63
|
+
end
|
64
|
+
|
65
|
+
def pull
|
66
|
+
return self if @own
|
67
|
+
|
68
|
+
# This is kinda wonky, obviously
|
69
|
+
# since the @fq already called set_as_active on an object
|
70
|
+
if @fq.pull_job(@name)
|
71
|
+
@own = true
|
72
|
+
@path = @fq.run_que_path + @name
|
73
|
+
@status = ST_RUN
|
74
|
+
@owning_pid = $$
|
75
|
+
return self
|
76
|
+
end
|
77
|
+
return nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def set_as_active
|
81
|
+
@own = true
|
82
|
+
# Record
|
83
|
+
@path = @fq.run_que_path + @name
|
84
|
+
@status = ST_RUN
|
85
|
+
File.open(@path + '/pid', "w") { |f| f.write("#{$$}\n") }
|
86
|
+
@owning_pid = $$
|
87
|
+
end
|
88
|
+
|
89
|
+
# TODO: this could be stale
|
90
|
+
def status
|
91
|
+
return @status if @own
|
92
|
+
return @fq.status(@name)
|
93
|
+
end
|
94
|
+
|
95
|
+
# TODO: this could be stale
|
96
|
+
def status_mesg
|
97
|
+
return @status_mesg if @own
|
98
|
+
return @fq.status_mesg_for_job(@name)
|
99
|
+
end
|
100
|
+
|
101
|
+
# The @fq locks and then does a callback here
|
102
|
+
def callback_status_mesg
|
103
|
+
data = ''
|
104
|
+
File.open(@path + '/status', "r") do
|
105
|
+
|f|
|
106
|
+
data = f.read
|
107
|
+
end
|
108
|
+
return data
|
109
|
+
end
|
110
|
+
|
111
|
+
def status_mesg=(msg)
|
112
|
+
raise WrongStatus.new('cannot set status mesg on non-owned job') unless @own
|
113
|
+
@status_mesg = msg
|
114
|
+
File.open(@path + '/status', "w") { |f| f.write(msg) }
|
115
|
+
end
|
116
|
+
|
117
|
+
def status_all
|
118
|
+
return [@status, @owning_pid, status_mesg]
|
119
|
+
end
|
120
|
+
|
121
|
+
def log(msg)
|
122
|
+
raise WrongStatus.new('cannot log on non-owned job') unless @own
|
123
|
+
File.open(@path + '/log', "a") do
|
124
|
+
|f|
|
125
|
+
log_msg = Time.now.to_s + " == " + msg + "\n"
|
126
|
+
f.write(log_msg)
|
127
|
+
@logs << log_msg
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def read_log
|
132
|
+
return @logs.join('') if @own
|
133
|
+
return @fq.status_logs_for_job(@name)
|
134
|
+
end
|
135
|
+
|
136
|
+
# The @fq locks and then does a callback here
|
137
|
+
def callback_read_logs
|
138
|
+
data = ''
|
139
|
+
File.open(@path + '/log', "r") do
|
140
|
+
|f|
|
141
|
+
data = f.read
|
142
|
+
end
|
143
|
+
return data
|
144
|
+
end
|
145
|
+
|
146
|
+
def mark_done
|
147
|
+
raise WrongStatus.new('cannot mark job as done on non-owned job') unless @own
|
148
|
+
status_mesg = "finishing"
|
149
|
+
@fq.mark_job_done(self)
|
150
|
+
end
|
151
|
+
|
152
|
+
def mark_error
|
153
|
+
raise WrongStatus.new('cannot mark job as error on non-owned job') unless @own
|
154
|
+
status_mesg = "finishing"
|
155
|
+
@fq.mark_job_error(self)
|
156
|
+
end
|
157
|
+
|
158
|
+
def reinsert
|
159
|
+
raise WrongStatus.new('cannot reinsert on non-owned job') unless @own
|
160
|
+
status_mesg = "reinserting"
|
161
|
+
log("reinserting job")
|
162
|
+
@fq.reinsert_job(self)
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
def initialize(queue, name, path, status)
|
168
|
+
@fq = queue
|
169
|
+
@name = name
|
170
|
+
@path = path
|
171
|
+
@own = false
|
172
|
+
@owning_pid = nil
|
173
|
+
@status = status
|
174
|
+
@status_mesg = ''
|
175
|
+
@logs = []
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
|
data/lib/lockfile.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# LockFile`
|
2
|
+
#
|
3
|
+
# This assumes that a single threaded process is using
|
4
|
+
# this object to use a file on disk as a lock/mutex
|
5
|
+
# for synchronization.
|
6
|
+
#
|
7
|
+
# class User < ActiveRecord::Base
|
8
|
+
# acts_as_audited
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# See <tt>CollectiveIdea::Acts::Audited::ClassMethods#acts_as_audited</tt>
|
12
|
+
# for configuration options
|
13
|
+
#http://rails.techno-weenie.net/tip/2005/11/19/validate_your_forms_with_a_table_less_model
|
14
|
+
#http://lists.vanruby.com/pipermail/discuss/2006-January/000050.html
|
15
|
+
|
16
|
+
# class Order < InActiveRecord
|
17
|
+
# column :id, :integer
|
18
|
+
# column :name, :string
|
19
|
+
# column :address, :string
|
20
|
+
# ...
|
21
|
+
# validates_presence_of :name, :address ....
|
22
|
+
#
|
23
|
+
#
|
24
|
+
|
25
|
+
require 'fcntl'
|
26
|
+
|
27
|
+
module Xxeo
|
28
|
+
|
29
|
+
class LockFile
|
30
|
+
|
31
|
+
def initialize(pathname, options = {})
|
32
|
+
options[:env] ||= 'development'
|
33
|
+
|
34
|
+
raise "lockfile not writable" if not File.writable? pathname
|
35
|
+
|
36
|
+
@f = File.open(pathname, 'r')
|
37
|
+
|
38
|
+
raise "could not open file" if not @f
|
39
|
+
|
40
|
+
@lock_count = 0
|
41
|
+
end
|
42
|
+
|
43
|
+
# We allow a process that holds a lock to recursively
|
44
|
+
# acquire the lock, the allows the higher level programs
|
45
|
+
# to not have to keep track of the lock status
|
46
|
+
# This is becase the flock mechanism doesn't do reference
|
47
|
+
# counting.
|
48
|
+
|
49
|
+
def lock
|
50
|
+
# already locked, just increase the ref count
|
51
|
+
if @lock_count > 0
|
52
|
+
@lock_count += 1
|
53
|
+
return
|
54
|
+
end
|
55
|
+
|
56
|
+
# THIS COULD BLOCK
|
57
|
+
@f.flock(File::LOCK_EX)
|
58
|
+
@lock_count = 1
|
59
|
+
end
|
60
|
+
|
61
|
+
def unlock
|
62
|
+
if @lock_count > 1
|
63
|
+
@lock_count -= 1
|
64
|
+
return
|
65
|
+
end
|
66
|
+
|
67
|
+
if @lock_count == 1
|
68
|
+
@f.flock(File::LOCK_UN)
|
69
|
+
@lock_count -= 1
|
70
|
+
return
|
71
|
+
end
|
72
|
+
|
73
|
+
if @lock_count < 1
|
74
|
+
raise "Attempt to unlock an unlocked LockFile"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def locked?
|
79
|
+
return @lock_count > 0
|
80
|
+
end
|
81
|
+
|
82
|
+
def lock_count
|
83
|
+
return @lock_count
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
#require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
|
2
|
+
|
3
|
+
|
4
|
+
class FileQCreateDirTests < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@fq = Xxeo::FileQ.new('test', {:dir => 'test/test_fq'})
|
8
|
+
@fq.create_queue_dirs
|
9
|
+
end
|
10
|
+
|
11
|
+
def teardown
|
12
|
+
FileUtils.rm_rf 'test/test_fq'
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_create
|
16
|
+
assert(FileTest.directory?('test/test_fq'))
|
17
|
+
assert(FileTest.writable?('test/test_fq'))
|
18
|
+
assert(FileTest.readable?('test/test_fq/_lock'))
|
19
|
+
assert(FileTest.writable?('test/test_fq/_log'))
|
20
|
+
assert(FileTest.directory?('test/test_fq/_tmp'))
|
21
|
+
assert(FileTest.directory?('test/test_fq/que'))
|
22
|
+
assert(FileTest.directory?('test/test_fq/run'))
|
23
|
+
assert(FileTest.directory?('test/test_fq/pause'))
|
24
|
+
assert(FileTest.directory?('test/test_fq/done'))
|
25
|
+
assert(FileTest.directory?('test/test_fq/_err'))
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_create_verify
|
29
|
+
assert(@fq.verify_store, 'Failed to verify')
|
30
|
+
assert_equal('', @fq.last_error, 'Failed to have empty error message')
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_bad_lock_file
|
34
|
+
FileUtils.rm_rf('test/test_fq/_lock')
|
35
|
+
assert(! @fq.verify_store, 'Failed to verify as false')
|
36
|
+
assert_equal('bad queue dir: file \'_lock\' does not exist', @fq.last_error, 'Failed to have correct error message')
|
37
|
+
end
|
38
|
+
|
39
|
+
#TODO: test lock file writable
|
40
|
+
|
41
|
+
def test_bad_log_file
|
42
|
+
FileUtils.rm_rf('test/test_fq/_log')
|
43
|
+
assert(! @fq.verify_store, 'Failed to verify as false')
|
44
|
+
assert_equal('bad queue dir: file \'_log\' does not exist', @fq.last_error, 'Failed to have correct error message')
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_bad_tmp_dir
|
48
|
+
FileUtils.rm_rf('test/test_fq/_tmp')
|
49
|
+
assert(! @fq.verify_store, 'Failed to verify as false')
|
50
|
+
assert_equal('bad queue dir: \'_tmp\' not a directory', @fq.last_error, 'Failed to have correct error message')
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_bad_q_dir
|
54
|
+
FileUtils.rm_rf('test/test_fq/que')
|
55
|
+
assert(! @fq.verify_store, 'Failed to verify as false')
|
56
|
+
assert_equal('bad queue dir: \'que\' not a directory', @fq.last_error, 'Failed to have correct error message')
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_bad_run_dir
|
60
|
+
FileUtils.rm_rf('test/test_fq/run')
|
61
|
+
assert(! @fq.verify_store, 'Failed to verify as false')
|
62
|
+
assert_equal('bad queue dir: \'run\' not a directory', @fq.last_error, 'Failed to have correct error message')
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_bad_pause_dir
|
66
|
+
FileUtils.rm_rf('test/test_fq/pause')
|
67
|
+
assert(! @fq.verify_store, 'Failed to verify as false')
|
68
|
+
assert_equal('bad queue dir: \'pause\' not a directory', @fq.last_error, 'Failed to have correct error message')
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_bad_done_dir
|
72
|
+
FileUtils.rm_rf('test/test_fq/done')
|
73
|
+
assert(! @fq.verify_store, 'Failed to verify as false')
|
74
|
+
assert_equal('bad queue dir: \'done\' not a directory', @fq.last_error, 'Failed to have correct error message')
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_bad_err_dir
|
78
|
+
FileUtils.rm_rf('test/test_fq/_err')
|
79
|
+
assert(! @fq.verify_store, 'Failed to verify as false')
|
80
|
+
assert_equal('bad queue dir: \'_err\' not a directory', @fq.last_error, 'Failed to have correct error message')
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
|
2
|
+
class FileQFindTests < Test::Unit::TestCase
|
3
|
+
|
4
|
+
def setup
|
5
|
+
@fq = Xxeo::FileQ.new('test', {:dir => 'test/test_fq'})
|
6
|
+
@fq.create_queue_dirs
|
7
|
+
|
8
|
+
@tmp_fname1 = 'tmp_file1'
|
9
|
+
@tmp_name1 = 'test/test_fq/' + @tmp_fname1
|
10
|
+
@data1 = "blah blah1\nblah blah1\n"
|
11
|
+
File.open(@tmp_name1, "w") { |f| f.write(@data1) }
|
12
|
+
|
13
|
+
@tmp_fname2 = 'tmp_file2'
|
14
|
+
@tmp_name2 = 'test/test_fq/' + @tmp_fname2
|
15
|
+
@data2 = "blah blah2\nblah blah2\n"
|
16
|
+
File.open(@tmp_name2, "w") { |f| f.write(@data2) }
|
17
|
+
|
18
|
+
@job1 = @fq.insert_data('This is a test')
|
19
|
+
end
|
20
|
+
|
21
|
+
def teardown
|
22
|
+
FileUtils.rm_rf 'test/test_fq'
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_find
|
26
|
+
assert(@fq.find_job(@job1.name))
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_find_fail
|
30
|
+
assert(!@fq.find_job('dldldld'))
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_find_que
|
34
|
+
job = @fq.find_job(@job1.name)
|
35
|
+
assert(job)
|
36
|
+
assert_equal(job.status, Xxeo::ST_QUEUED)
|
37
|
+
assert_equal(false, job.own?)
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_find_run
|
41
|
+
@job1.pull
|
42
|
+
job = @fq.find_job(@job1.name)
|
43
|
+
assert(job)
|
44
|
+
assert_equal(job.status, Xxeo::ST_RUN)
|
45
|
+
assert_equal(false, job.own?)
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_find_and_run
|
49
|
+
job = @fq.find_job(@job1.name)
|
50
|
+
assert(job)
|
51
|
+
assert(job.pull, @fq.read_log)
|
52
|
+
assert_equal(job.status, Xxeo::ST_RUN)
|
53
|
+
assert_equal(true, job.own?)
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_find_done
|
57
|
+
@job1.pull
|
58
|
+
@job1.mark_done
|
59
|
+
job = @fq.find_job(@job1.name)
|
60
|
+
assert(job)
|
61
|
+
assert_equal(job.status, Xxeo::ST_DONE)
|
62
|
+
assert_equal(false, job.own?)
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_find_err
|
66
|
+
@job1.pull
|
67
|
+
@job1.mark_error
|
68
|
+
job = @fq.find_job(@job1.name)
|
69
|
+
assert(job)
|
70
|
+
assert_equal(job.status, Xxeo::ST_ERROR)
|
71
|
+
assert_equal(false, job.own?)
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
|
2
|
+
|
3
|
+
class FileQFirstTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
FileUtils.mkdir 'test/test_fq'
|
7
|
+
FileUtils.touch 'test/test_fq/_lock'
|
8
|
+
FileUtils.touch 'test/test_fq/_log'
|
9
|
+
FileUtils.mkdir 'test/test_fq/_tmp'
|
10
|
+
FileUtils.mkdir 'test/test_fq/que'
|
11
|
+
FileUtils.mkdir 'test/test_fq/run'
|
12
|
+
FileUtils.mkdir 'test/test_fq/pause'
|
13
|
+
FileUtils.mkdir 'test/test_fq/done'
|
14
|
+
FileUtils.mkdir 'test/test_fq/_err'
|
15
|
+
File.open('test/test_fq/lock', "w") { |f| f.close }
|
16
|
+
end
|
17
|
+
|
18
|
+
def teardown
|
19
|
+
FileUtils.rm_rf 'test/test_fq'
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_instantiate
|
23
|
+
fq = Xxeo::FileQ.new('test', { :dir => 'test/test_fq'})
|
24
|
+
assert_not_nil(fq)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_instantiate_verify
|
28
|
+
fq = Xxeo::FileQ.new('test', { :dir => 'test/test_fq'})
|
29
|
+
assert_not_nil(fq)
|
30
|
+
assert(fq.verify_store)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|