rask 0.0.0 → 0.0.1

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.
Files changed (3) hide show
  1. data/README.rdoc +32 -26
  2. data/lib/rask.rb +84 -24
  3. metadata +3 -5
data/README.rdoc CHANGED
@@ -9,30 +9,37 @@
9
9
  タスクの実行状態が自動的に永続化されるので、
10
10
  タスクの中断、復帰が容易です。
11
11
  これらの処理は、web のように処理がまばらな環境での利用を想定しています。
12
+ どうしてもサーバを再起動しなければいけない時など、バックエンドタスクの中断、復帰を安全に実装できます。
12
13
  また、膨大な時間がかかる計算処理などでの利用も考えられます。
13
14
 
14
15
 
15
-
16
16
  == FEATURES/PROBLEMS:
17
17
 
18
- * ステートマシンの記述 (Act As State Machine と似てる)
18
+ * シンプルなステートマシンの記述 (Act As State Machine と似てる)
19
19
  * タスク状態の永続化
20
+ * デーモン起動によるブロッキング回避
21
+ * たとえば、webなんかだとタイムアウト回避処理が容易に記述できる
22
+ * webビデオエンコーダとか、サイトの大量メール配信とか
23
+ * デーモンが落ちても永続化されているので処理の途中から復帰可能
24
+ * kill で落とした場合は安全に状態を維持したまま終了できる
25
+ * 永続化はMarshal使っているだけなので drb 組み合わせて、強力な web のバックエンド構築ができる気がするが試していない
26
+ * 内部動作はワーカースレッドで処理しており、スレッド数も変更できるので、処理に応じたチューンが可能
27
+ * CPUパワーやメモリ量から逆算して、並列化の度合いを調整することでアホみたいにサーバが重たい状況も回避できる。
20
28
 
21
29
  == SYNOPSIS:
22
30
 
23
- * タスクの定義 (countup.rb)
31
+ * タスクの定義と実行 (samples/test.rb)
24
32
  require 'rubygems'
25
33
  require 'rask'
26
- #
27
- # 数値のカウントアップ 10 までカウントする
28
- # タスクの定義
29
- #
34
+
35
+ # 数値のカウントアップ 10 までカウントするタスク
30
36
  class CountupTask < Rask::Task
37
+ # define_state でステートマシンを作ることができます
31
38
  define_state :start, :initial => true # 初期状態
32
39
  define_state :running # 実行
33
40
  define_state :finish, :from => [:running] # 終了 (遷移元は:runningからのみ)
34
41
 
35
- def start
42
+ def start # 定義と同名の関数を定義することで自動的にコールバックされます
36
43
  @count = 0
37
44
  p "start"
38
45
  transition_to_running # :running へ遷移
@@ -45,31 +52,30 @@
45
52
 
46
53
  def finish
47
54
  p "finished"
48
- destroy # タスクの破棄
55
+ destroy # タスクの破棄をする
49
56
  end
50
57
  end
51
58
 
59
+ task = CountupTask.new # タスクの作成
60
+ Rask.insert task # タスクの登録
52
61
 
53
- # タスクを動かす
54
- if __FILE__ == $0
55
- task = CountupTask.new # タスクの作成
56
- Rask.insert task # タスクの登録
57
- end
58
-
59
-
60
- * タスクを実行するデーモン(daemon.rb)
61
- require 'rubygems'
62
- require 'rask'
63
-
64
- # バックグラウンドタスクの実行
65
- Rask.daemon(:sleep=>0.1)
62
+ Rask.daemon # デーモンとして実行
63
+
64
+ タスク定義と、デーモンの実行コードがそろったら以下のようにデーモンを起動
65
+
66
+ $ ruby samples/test.rb
67
+
68
+ test.rb を実行するたびにタスクが新しく登録され実行する様子がわかる
69
+ Raskはデーモンの二重起動を抑制するので複数のプロセスが立ち上がることはない
66
70
 
71
+ デーモンが起動すると、プロセスIDファイルが rask の作業ディレクトリに自動的に生成されるので
72
+ デーモンの終了は以下のようにできる
67
73
 
68
- デーモンを実行しておくと、Rask.insert を行うだけで
69
- タスクが自動的に実行されていきます。
74
+ $ kill `cat /tmp/rask/test.rb.pid`
70
75
 
71
- $ ruby daemon.rb
72
- $ ruby countup.rb
76
+ kill -TERM されたプロセスは、そのタスクのステートマシンの状態を壊さない。
77
+ いつでも、test.rb を再実行することで前回中断したところから続きのタスクを実行することができる。
78
+ シビアな運用環境で役立つはず。
73
79
 
74
80
  == REQUIREMENTS:
75
81
 
data/lib/rask.rb CHANGED
@@ -45,9 +45,15 @@ module Rask
45
45
  end
46
46
 
47
47
 
48
- @@base_dir = '/tmp/rask'
49
- @@threading = false
50
-
48
+ @@base_dir = '/tmp/rask'
49
+ @@threading = false
50
+ @@thread_max_count = 5
51
+ @@thread_count = 0
52
+ @@terminated = false
53
+ @@queue = Queue.new
54
+ @@processing = []
55
+ @@locker = Mutex::new
56
+
51
57
  def self.base_directory=(new_directory)
52
58
  @@base_dir = new_directory
53
59
  end
@@ -60,6 +66,22 @@ module Rask
60
66
  @@threading = false
61
67
  end
62
68
 
69
+ def self.thread_max_count=(count)
70
+ @@thread_max_count = count
71
+ end
72
+
73
+ def self.task_path(task_id)
74
+ @@base_dir+"/#{task_id}.task"
75
+ end
76
+
77
+ def self.task_path(task_id)
78
+ @@base_dir+"/#{task_id}.task"
79
+ end
80
+
81
+ def self.pid_path
82
+ @@base_dir+"/#{File.basename($0)}.pid"
83
+ end
84
+
63
85
  def self.insert(task)
64
86
  initialize_storage
65
87
  task_id = "#{safe_class_name(task.class.name)}-#{task.group.to_s}-#{Time.now.to_i}-#{Time.now.usec}"
@@ -74,7 +96,7 @@ module Rask
74
96
 
75
97
 
76
98
  def self.run(task_path)
77
- f = File.open(task_path, 'r+')
99
+ f = File.open(task_path, 'r+') rescue return
78
100
  f.flock(File::LOCK_EX)
79
101
 
80
102
  task = Marshal.restore(f)
@@ -101,13 +123,13 @@ module Rask
101
123
  end
102
124
 
103
125
  def self.tasks(options = { :class=>nil, :group=>nil })
104
- target = task_dir
126
+ target = @@base_dir
105
127
  target += '/' if options[:class] || options[:group]
106
128
  target += "#{safe_class_name(options[:class])}" if options[:class]
107
129
  target += "-#{options[:group]}-" if options[:group]
108
130
 
109
131
  task_list = []
110
- Dir.glob(task_dir+"/*.task") { |d|
132
+ Dir.glob(@@base_dir+"/*.task") { |d|
111
133
  if target.empty? || /#{target}/ =~ d
112
134
  task_list.push d
113
135
  end
@@ -115,14 +137,6 @@ module Rask
115
137
  task_list
116
138
  end
117
139
 
118
- def self.task_dir
119
- @@base_dir
120
- end
121
-
122
- def self.task_path(task_id)
123
- task_dir+"/#{task_id}.task"
124
- end
125
-
126
140
  def self.initialize_storage
127
141
  unless File.exists? @@base_dir
128
142
  FileUtils.makedirs @@base_dir
@@ -136,23 +150,69 @@ module Rask
136
150
  def self.safe_class_name(c)
137
151
  c.gsub(/[:]/,'@')
138
152
  end
139
-
140
- def self.daemon(options = { :class=>nil, :group=>nil, :sleep=>1 })
141
- exit if fork
153
+
154
+ def self.daemon(options = { :class=>nil, :group=>nil, :sleep=>0.1 })
155
+ print "daemon start\n"
156
+ exit if fork
142
157
  Process.setsid
143
- open(task_dir+"/Rask.pid","w"){|f| f.write Process.pid}
158
+ if File.exist? pid_path
159
+ print "already running rask process. #{File.basename($0)}"
160
+ return
161
+ end
162
+ open(pid_path,"w"){|f| f.write Process.pid}
163
+
164
+ # create worker threads
165
+ threads = []
166
+ for i in 1..@@thread_max_count do
167
+ threads << Thread::new(i) { |thread_id|
168
+ @@thread_count += 1
169
+ while !@@terminated
170
+ d = nil
171
+ @@locker.synchronize do
172
+ d = @@queue.pop unless @@queue.empty?
173
+ end
174
+ if d != nil
175
+ # print "#{d}\n"
176
+ run(d) { |task| task.run }
177
+ @@locker.synchronize do
178
+ @@processing.delete(d)
179
+ end
180
+ else
181
+ # print "no data in queue\n"
182
+ sleep(options[:sleep])
183
+ end
184
+ end
185
+ print "#{thread_id}"
186
+ @@thread_count -= 1
187
+ }
188
+ end
189
+
190
+ Signal.trap(:TERM) {safe_exit}
191
+
144
192
  while true
145
- Rask.each { |task|
146
- task.run
193
+ task_list = Rask.tasks(options)
194
+ task_list.each { |d|
195
+ @@locker.synchronize do
196
+ unless @@processing.include?(d)
197
+ @@queue.push d
198
+ @@processing.push d
199
+ end
200
+ end
147
201
  }
148
202
  sleep(options[:sleep])
149
203
  end
150
204
  end
151
205
 
206
+ def self.safe_exit
207
+ print "daemon terminated"
208
+ @@terminated = true
209
+ while @@thread_count > 0
210
+ sleep(0.1)
211
+ end
212
+ FileUtils.rm(pid_path) if File.exist?(pid_path)
213
+ print "done \n"
214
+ exit
215
+ end
152
216
 
153
217
  end
154
218
 
155
-
156
-
157
-
158
-
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rask
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - mewlist / Hidenori Doi
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-02-11 00:00:00 +09:00
12
+ date: 2010-02-26 00:00:00 +09:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -56,8 +56,6 @@ rubyforge_project:
56
56
  rubygems_version: 1.3.5
57
57
  signing_key:
58
58
  specification_version: 3
59
- summary: !binary |
60
- 44K/44K544Kv44Op44Kk44OW44Op44Oq
61
-
59
+ summary: Terminatable Task Engine
62
60
  test_files: []
63
61