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.
- data/README.rdoc +32 -26
- data/lib/rask.rb +84 -24
- 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 | 
            -
            *  | 
| 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 | 
            -
            *  | 
| 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 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
             
         | 
| 60 | 
            -
             | 
| 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 | 
            -
             | 
| 69 | 
            -
            タスクが自動的に実行されていきます。
         | 
| 74 | 
            +
             $ kill `cat /tmp/rask/test.rb.pid`
         | 
| 70 75 |  | 
| 71 | 
            -
              | 
| 72 | 
            -
             | 
| 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 | 
| 49 | 
            -
              @@threading | 
| 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 =  | 
| 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( | 
| 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 | 
            -
                 | 
| 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 | 
            -
                 | 
| 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. | 
| 146 | 
            -
             | 
| 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. | 
| 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- | 
| 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:  | 
| 60 | 
            -
              44K/44K544Kv44Op44Kk44OW44Op44Oq
         | 
| 61 | 
            -
             | 
| 59 | 
            +
            summary: Terminatable Task Engine
         | 
| 62 60 | 
             
            test_files: []
         | 
| 63 61 |  |