euston-daemons 1.0.5 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/Gemfile +4 -1
  2. data/Rakefile +2 -3
  3. data/euston-daemons.gemspec +50 -39
  4. data/lib/euston-daemons.rb +14 -1
  5. data/lib/euston-daemons/euston/daemon.rb +99 -0
  6. data/lib/euston-daemons/euston/daemon_component.rb +25 -0
  7. data/lib/euston-daemons/euston/daemon_component_host.rb +66 -0
  8. data/lib/euston-daemons/euston/daemon_environment.rb +59 -0
  9. data/lib/euston-daemons/euston/exceptions.rb +9 -0
  10. data/lib/euston-daemons/euston/stopwatch.rb +15 -0
  11. data/lib/euston-daemons/pipeline/config/environment.rb +78 -0
  12. data/lib/euston-daemons/pipeline/lib/command_logger/component.rb +54 -0
  13. data/lib/euston-daemons/pipeline/lib/command_logger/log.rb +31 -0
  14. data/lib/euston-daemons/pipeline/lib/command_processor/component.rb +50 -0
  15. data/lib/euston-daemons/pipeline/lib/command_processor/default_commands/retry_failed_message.rb +13 -0
  16. data/lib/euston-daemons/pipeline/lib/command_processor/default_handlers/retry_failed_message.rb +34 -0
  17. data/lib/euston-daemons/pipeline/lib/command_processor/failed_message.rb +36 -0
  18. data/lib/euston-daemons/pipeline/lib/daemon.rb +85 -0
  19. data/lib/euston-daemons/pipeline/lib/event_processor/component.rb +67 -0
  20. data/lib/euston-daemons/pipeline/lib/event_processor/default_handlers/message_failure.rb +30 -0
  21. data/lib/euston-daemons/pipeline/lib/event_store_dispatcher/component.rb +68 -0
  22. data/lib/euston-daemons/pipeline/lib/message_buffer/buffer.rb +85 -0
  23. data/lib/euston-daemons/pipeline/lib/message_buffer/component.rb +59 -0
  24. data/lib/euston-daemons/pipeline/lib/snapshotter/component.rb +48 -0
  25. data/lib/euston-daemons/pipeline/rake_task.rb +49 -0
  26. data/lib/euston-daemons/rake_task.rb +63 -66
  27. data/lib/euston-daemons/rake_tasks.rb +3 -5
  28. data/lib/euston-daemons/version.rb +1 -1
  29. data/spec/daemons/command_processor_spec.rb +48 -0
  30. data/spec/daemons/event_processor_spec.rb +55 -0
  31. data/spec/daemons/message_buffer_spec.rb +106 -0
  32. data/spec/daemons/snapshotter_spec.rb +96 -0
  33. data/spec/spec_helper.rb +91 -0
  34. data/spec/support/factories/commands.rb +16 -0
  35. data/spec/support/factories/commit.rb +7 -0
  36. data/spec/support/factories/event_message.rb +12 -0
  37. data/spec/support/factories/events.rb +8 -0
  38. data/spec/support/filters.rb +13 -0
  39. data/spec/support/sample_model/commands.rb +14 -0
  40. data/spec/support/sample_model/counter.rb +36 -0
  41. data/spec/support/sample_model/counter2.rb +46 -0
  42. data/spec/support/stub_retrying_subscription.rb +9 -0
  43. metadata +131 -67
  44. data/lib/euston-daemons/command_processor_daemon/config/environment.rb +0 -25
  45. data/lib/euston-daemons/command_processor_daemon/lib/components/command_handler_component.rb +0 -56
  46. data/lib/euston-daemons/command_processor_daemon/lib/daemon.rb +0 -43
  47. data/lib/euston-daemons/command_processor_daemon/lib/settings.rb +0 -22
  48. data/lib/euston-daemons/command_processor_daemon/rake_task.rb +0 -34
  49. data/lib/euston-daemons/event_processor_daemon/config/environment.rb +0 -25
  50. data/lib/euston-daemons/event_processor_daemon/lib/components/event_handler_component.rb +0 -58
  51. data/lib/euston-daemons/event_processor_daemon/lib/daemon.rb +0 -71
  52. data/lib/euston-daemons/event_processor_daemon/lib/settings.rb +0 -26
  53. data/lib/euston-daemons/event_processor_daemon/rake_task.rb +0 -37
  54. data/lib/euston-daemons/framework/basic_component.rb +0 -33
  55. data/lib/euston-daemons/framework/channel_thread.rb +0 -22
  56. data/lib/euston-daemons/framework/component_shutdown.rb +0 -22
  57. data/lib/euston-daemons/framework/daemon.rb +0 -27
  58. data/lib/euston-daemons/framework/handler_bindings_component.rb +0 -56
  59. data/lib/euston-daemons/framework/queue.rb +0 -71
  60. data/lib/euston-daemons/message_buffer_daemon/config/environment.rb +0 -28
  61. data/lib/euston-daemons/message_buffer_daemon/lib/components/buffer_component.rb +0 -73
  62. data/lib/euston-daemons/message_buffer_daemon/lib/components/event_store_component.rb +0 -52
  63. data/lib/euston-daemons/message_buffer_daemon/lib/daemon.rb +0 -48
  64. data/lib/euston-daemons/message_buffer_daemon/lib/message_logger.rb +0 -54
  65. data/lib/euston-daemons/message_buffer_daemon/lib/publisher.rb +0 -56
  66. data/lib/euston-daemons/message_buffer_daemon/lib/read_model/message_log.rb +0 -36
  67. data/lib/euston-daemons/message_buffer_daemon/lib/settings.rb +0 -14
  68. data/lib/euston-daemons/message_buffer_daemon/lib/subscriber.rb +0 -60
  69. data/lib/euston-daemons/message_buffer_daemon/rake_task.rb +0 -30
@@ -0,0 +1,30 @@
1
+ module Euston
2
+ module Daemons
3
+ module Pipeline
4
+ module EventProcessor
5
+ module DefaultHandlers
6
+ class MessageFailure
7
+ include Euston::EventHandler
8
+
9
+ subscribes :message_failed, 1 do |headers, event|
10
+ headers = event[:message][:headers].dup
11
+ failure = { :message_id => headers.delete(:id),
12
+ :type => headers.delete(:type),
13
+ :version => headers.delete(:version),
14
+ :message_timestamp => headers.delete(:timestamp),
15
+ :routing_key => event[:routing_key],
16
+ :body => event[:message][:body],
17
+ :headers => headers,
18
+ :error => event[:error],
19
+ :backtrace => event[:backtrace],
20
+ :failure_timestamp => Time.now.to_f }
21
+
22
+ failed_messages = CommandProcessor::FailedMessage.new DaemonEnvironment.event_store_mongodb
23
+ failed_messages.log_failure failure
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,68 @@
1
+ module Euston
2
+ module Daemons
3
+ module Pipeline
4
+ module EventStoreDispatcher
5
+ class Component < DaemonComponent
6
+ extend RabbitMq::Exchanges
7
+
8
+ def initialize channel, id = 1, log = Euston::NullLogger.instance
9
+ @channel = channel
10
+ @channel.tx_select
11
+ @id = id
12
+ @log = log
13
+ @event_store = DaemonEnvironment.event_store
14
+ @stopwatch = Stopwatch.new.when(:finished => method(:log_elapsed_time))
15
+ @commands_exchange = self.class.get_exchange channel, :commands
16
+ @events_exchange = self.class.get_exchange channel, :events
17
+ @buffer = MessageBuffer::Buffer.new DaemonEnvironment.event_store_mongodb
18
+ end
19
+
20
+ private
21
+
22
+ def log_elapsed_time elapsed_time
23
+ @log.debug "Event store dispatcher #{@id} dispatched #{@commits_dispatched} commit(s) in #{elapsed_time} sec(s)" if @commits_dispatched > 0
24
+ end
25
+
26
+ def next_iteration
27
+ @commits_dispatched = 0
28
+
29
+ @stopwatch.time do
30
+ begin
31
+ @event_store.take_ownership_of_undispatched_commits @id
32
+ @commits = @event_store.get_undispatched_commits @id
33
+
34
+ begin
35
+ @commits.each do |commit|
36
+ commit.events.each do |event|
37
+ hash = event.to_hash
38
+ @events_exchange.publish ActiveSupport::JSON.encode(hash), self.class.default_publish_options.merge(:routing_key => "events.#{hash[:headers][:type]}")
39
+ end
40
+
41
+ commit.commands.each do |command|
42
+ hash = command.to_hash
43
+
44
+ if hash[:headers][:dispatch_at].nil?
45
+ @commands_exchange.publish ActiveSupport::JSON.encode(hash), self.class.default_publish_options.merge(:routing_key => "commands.#{hash[:headers][:type]}")
46
+ else
47
+ @buffer.enqueue :commands, hash, hash[:headers][:dispatch_at]
48
+ end
49
+ end
50
+ end
51
+
52
+ @channel.tx_commit
53
+ rescue StandardError => e
54
+ @channel.tx_rollback
55
+ raise e
56
+ end
57
+
58
+ @event_store.mark_commits_as_dispatched @commits
59
+
60
+ @commits_dispatched += @commits.size
61
+ end until stopped || @commits.empty?
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,85 @@
1
+ module Euston
2
+ module Daemons
3
+ module Pipeline
4
+ module MessageBuffer
5
+ class Buffer
6
+ def initialize mongodb
7
+ name = 'message_buffer'
8
+ mongodb.create_collection name unless mongodb.collection_names.include? name
9
+
10
+ @name = name
11
+ @collection = mongodb.collection name
12
+
13
+ @collection.ensure_index [ ['message_id', Mongo::ASCENDING] ],
14
+ :unique => false,
15
+ :name => "#{name}_message_id_index"
16
+
17
+ @collection.ensure_index [ ['component_id', Mongo::ASCENDING],
18
+ ['dispatch_at', Mongo::ASCENDING] ],
19
+ :unique => false,
20
+ :name => "#{name}_component_id_dispatch_at_index"
21
+ end
22
+
23
+ attr_reader :name
24
+
25
+ def delete_dispatched_messages component_id
26
+ @collection.remove({ 'component_id' => component_id }, :multi => true)
27
+ end
28
+
29
+ def enqueue exchange, message, dispatch_at = nil
30
+ messages = message
31
+ messages = [{ :hash => message, :dispatch_at => dispatch_at }] unless messages.is_a? Array
32
+
33
+ messages = messages.map do |m|
34
+ message_is_well_formed = m.is_a?(Hash) && m.has_key?(:hash) && m.has_key?(:dispatch_at)
35
+ m = { :hash => m, :dispatch_at => dispatch_at } unless message_is_well_formed
36
+ map_to_document exchange, m
37
+ end
38
+
39
+ @collection.insert(messages) unless messages.empty?
40
+ end
41
+
42
+ def find_dispatchable_messages component_id
43
+ query = { 'component_id' => component_id }
44
+ fields = ['exchange', 'type', 'json']
45
+ sort = [ 'dispatch_at', Mongo::ASCENDING ]
46
+
47
+ @collection.find query, :fields => fields, :sort => sort
48
+ end
49
+
50
+ def get_by_id id
51
+ @collection.find_one 'message_id' => id
52
+ end
53
+
54
+ def take_ownership_of_dispatchable_messages component_id
55
+ new_messages_eligible_for_dispatch = { 'component_id' => '',
56
+ 'dispatch_at' => { '$lte' => Time.now.to_f } }
57
+
58
+ messages_stuck_in_other_components = { 'component_id' => { '$ne' => '' },
59
+ 'dispatch_at' => { '$lte' => Time.now.to_f - 60 } }
60
+
61
+ query = { '$or' => [ new_messages_eligible_for_dispatch, messages_stuck_in_other_components ] }
62
+ doc = { '$set' => { 'component_id' => component_id } }
63
+
64
+ @collection.update query, doc, :multi => true
65
+ end
66
+
67
+ private
68
+
69
+ def map_to_document exchange, message
70
+ hash = message[:hash]
71
+ dispatch_at = (message[:dispatch_at] || Time.now.to_f).to_f
72
+
73
+ { 'message_id' => hash[:headers][:id],
74
+ 'exchange' => exchange,
75
+ 'type' => hash[:headers][:type],
76
+ 'component_id' => '',
77
+ 'dispatch_at' => dispatch_at,
78
+ 'dispatch_at_for_humans' => Time.at(dispatch_at),
79
+ 'json' => ActiveSupport::JSON.encode(hash) }
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,59 @@
1
+ module Euston
2
+ module Daemons
3
+ module Pipeline
4
+ module MessageBuffer
5
+ class Component < Euston::DaemonComponent
6
+ extend RabbitMq::Exchanges
7
+
8
+ def initialize channel, id = 1, logger = Euston::NullLogger.instance
9
+ @channel = channel
10
+ @channel.tx_select
11
+ @id = "message_buffer #{id}"
12
+ @log = logger
13
+ @buffer = Buffer.new DaemonEnvironment.event_store_mongodb
14
+ @stopwatch = Stopwatch.new.when(:finished => method(:log_elapsed_time))
15
+ end
16
+
17
+ private
18
+
19
+ def dispatch_due_messages
20
+ @dispatched_count = 0
21
+
22
+ @stopwatch.time do
23
+ @buffer.take_ownership_of_dispatchable_messages @id
24
+
25
+ begin
26
+ @buffer.find_dispatchable_messages(@id).each do |message|
27
+ exchange = self.class.get_exchange @channel, message['exchange']
28
+ exchange.publish message['json'], self.class.default_publish_options.merge(:routing_key => "#{exchange.name}.#{message['type']}")
29
+ @dispatched_count += 1
30
+ end
31
+
32
+ @channel.tx_commit
33
+ rescue StandardError => e
34
+ @channel.tx_rollback
35
+ raise e
36
+ end
37
+
38
+ @buffer.delete_dispatched_messages @id
39
+ end
40
+
41
+ @dispatched_count
42
+ end
43
+
44
+ def log_elapsed_time elapsed_time
45
+ @log.debug "Message buffer #{@id} dispatched #{@dispatched_count} message(s) in #{elapsed_time} sec(s)" if @dispatched_count > 0
46
+ end
47
+
48
+ def next_iteration
49
+ @messages_dispatched = 0
50
+
51
+ begin
52
+ @messages_dispatched = dispatch_due_messages
53
+ end until stopped || @messages_dispatched.zero?
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,48 @@
1
+ module Euston
2
+ module Daemons
3
+ module Pipeline
4
+ module Snapshotter
5
+ class Component < Euston::DaemonComponent
6
+ def initialize event_store, threshold, id = 1, logger = Euston::NullLogger.instance
7
+ @event_store = event_store
8
+ @threshold = threshold
9
+ @id = id
10
+ @log = logger
11
+ end
12
+
13
+ private
14
+
15
+ def next_iteration
16
+ begin
17
+ stream_heads = @event_store.get_streams_to_snapshot @threshold
18
+ @log.debug "Found #{stream_heads.length} stream(s) eligible for snapshotting (threshold is #{@threshold})" if stream_heads.any?
19
+
20
+ stream_heads.each do |stream_head|
21
+ pair = @event_store.get_snapshot_stream_pair stream_head.stream_id
22
+
23
+ loader = RabbitMq::ConstantLoader.new
24
+ loader.when(:hit => ->(klass) { take_snapshot klass, pair },
25
+ :miss => ->(type) { Safely.report! "Snapshotter was unable to find a class: #{type}" })
26
+
27
+ loader.load pair.stream.committed_headers[:aggregate_type]
28
+ end
29
+ end until stopped || stream_heads.empty?
30
+ end
31
+
32
+ def take_snapshot klass, pair
33
+ instance = klass.hydrate pair.stream, pair.snapshot
34
+ snapshot = instance.take_snapshot
35
+ snapshot = EventStore::Snapshot.new pair.stream.stream_id,
36
+ pair.stream.stream_revision,
37
+ snapshot[:payload],
38
+ :version => snapshot[:version]
39
+
40
+ @log.debug "Writing snapshot: #{snapshot.inspect}"
41
+
42
+ @event_store.add_snapshot snapshot
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,49 @@
1
+ module Euston
2
+ class PipelineRakeTask < Euston::Daemons::RakeTask
3
+ attr_accessor :amqp_config_path,
4
+ :command_handler_namespaces,
5
+ :daemon_config_path,
6
+ :event_handler_namespaces,
7
+ :mongo_config_path,
8
+ :user_defined_components
9
+
10
+ def initialize environment
11
+ super(environment, :pipeline_daemon)
12
+ end
13
+
14
+ def before_creating_task
15
+ @daemon_path = File.expand_path(File.dirname __FILE__) + File::SEPARATOR
16
+ @daemon_class = 'Euston::Daemons::Pipeline::Daemon'
17
+ end
18
+
19
+ def load_daemon_config_file
20
+ @data[:daemon_config] = ErbYaml.read @daemon_config_path, @data[:environment]
21
+ end
22
+
23
+ def initialize_settings
24
+ @logger.debug "AMQP config path: #{@amqp_config_path}"
25
+ @data[:amqp_config_path] = @amqp_config_path
26
+
27
+ @logger.debug "Command handler namespaces: #{@command_handler_namespaces}"
28
+ @data[:command_handler_namespaces] = @command_handler_namespaces
29
+
30
+ @logger.debug "Daemon config path: #{@daemon_config_path}"
31
+
32
+ @logger.debug "Event handler namespaces: #{@event_handler_namespaces}"
33
+ @data[:event_handler_namespaces] = @event_handler_namespaces
34
+
35
+ @logger.debug "Mongo config path: #{@mongo_config_path}"
36
+ @data[:mongo_config_path] = @mongo_config_path
37
+
38
+ @logger.debug "User-defined components: #{@user_defined_components.count}"
39
+ @data[:user_defined_components] = @user_defined_components.to_a.flatten
40
+ end
41
+
42
+ def load_environment
43
+ @logger.debug "Loading environment"
44
+ require 'euston-daemons/pipeline/config/environment'
45
+
46
+ Euston::Daemons::Pipeline::DaemonEnvironment.new(@data).setup
47
+ end
48
+ end
49
+ end
@@ -15,11 +15,8 @@ module Euston
15
15
  # Daemon class. Must be supplied as a string.
16
16
  attr_accessor :daemon_class
17
17
 
18
- # Log file path
19
- #
20
- # default:
21
- # /var/log/euston.log
22
- attr_accessor :log_path
18
+ # Callable. Receives the daemon environment object to allow the user to perform other related config operations.
19
+ attr_accessor :post_setup_callback
23
20
 
24
21
  # Use verbose output. If this is set to true, the task will print the
25
22
  # executed command to stdout.
@@ -28,65 +25,51 @@ module Euston
28
25
  # true
29
26
  attr_accessor :verbose
30
27
 
31
- def initialize(*args)
32
- @name = args.shift || :eustondaemon
33
- @log_path = '/var/log/euston.log'
28
+ def initialize environment, name = :euston_daemon
29
+ @name = name
34
30
  @verbose = true
35
-
36
- Object.const_set :EUSTON_ENV, get_environment unless Object.const_defined? :EUSTON_ENV
37
-
38
- @pid_path = pid_path_for_env + "#{@name}.pid"
31
+ @environment = validate_environment environment
32
+ @data = { :environment => @environment }
39
33
 
40
34
  yield self if block_given?
41
35
  send :before_creating_task if respond_to? :before_creating_task
42
36
 
43
37
  desc("Run a Euston daemon") unless ::Rake.application.last_comment
44
-
45
38
  task name do
46
39
  RakeFileUtils.send(:verbose, verbose) do
40
+ load_daemon_config_file
47
41
  initialize_logger
42
+ initialize_settings
43
+ write_pid_file
48
44
  log_startup
49
- initialize_paths
50
- load_framework
51
- load_environment
45
+ env = load_environment
46
+ post_setup_callback.call env unless post_setup_callback.nil?
52
47
  launch_and_wait_for_exit
53
48
  log_shutdown
49
+ remove_pid_file
54
50
  end
55
51
  end
56
52
  end
57
53
 
58
54
  private
59
55
 
60
- def get_environment
61
- environment = ENV['EUSTON_ENV'].to_s.downcase.to_sym
62
- environments = [:development, :test, :staging, :production]
63
- environment = :development unless environments.include? environment
64
- environment
65
- end
56
+ def initialize_logger
57
+ config = @data[:daemon_config]
58
+ log_path = config[:log_path]
66
59
 
67
- def pid_path_for_env
68
- case get_environment
69
- when :development, :test
70
- '~/var/run/euston/'
71
- else
72
- '/var/run/euston/'
73
- end
74
- end
60
+ raise "Required log path does not exist: #{log_path}" unless Dir.exist? log_path
75
61
 
76
- def initialize_logger
77
- Object.const_set :EUSTON_LOG, Logger.new(@log_path.gsub(/\.log$/, ".#{EUSTON_ENV}.log"))
78
- end
62
+ @data[:logger] = @logger = Logger.new(File.join log_path, "#{@name}.#{@environment}.log")
79
63
 
80
- def initialize_paths
81
- # virtual
64
+ begin
65
+ @logger.level = Logger.const_get config[:log_level].upcase.to_sym
66
+ rescue
67
+ @logger.level = Logger::DEBUG
68
+ end
82
69
  end
83
70
 
84
- def rake_pid
85
- if defined? Java
86
- Java::JavaIo::File.new("/proc/self").canonical_file.name
87
- else
88
- File.readlink("/proc/self")
89
- end
71
+ def initialize_settings
72
+ # virtual
90
73
  end
91
74
 
92
75
  def launch_and_wait_for_exit
@@ -97,26 +80,15 @@ module Euston
97
80
  ns = ns.const_get c.to_sym
98
81
  end
99
82
 
100
- daemon = ns.new
101
- create_pid_file
102
- trap_exit_signals daemon
103
- daemon.run
104
- remove_pid_file
83
+ ns.new(@data).run
105
84
  end
106
85
 
107
- def load_framework
108
- EUSTON_LOG.debug "Loading framework"
109
- require_rel 'framework'
110
- end
111
-
112
- def create_pid_file
113
- File.open( @pid_path, 'w' ) { |f|
114
- f.puts rake_pid
115
- }
86
+ def load_daemon_config_file
87
+ # virtual
116
88
  end
117
89
 
118
- def remove_pid_file
119
- File.delete( @pid_path ) rescue Errno::ENOENT
90
+ def load_environment
91
+ # virtual
120
92
  end
121
93
 
122
94
  def log_shutdown
@@ -130,17 +102,42 @@ module Euston
130
102
  def print_log_banner banner
131
103
  border = '-' * banner.length
132
104
 
133
- EUSTON_LOG.info ''
134
- EUSTON_LOG.info border
135
- EUSTON_LOG.info banner
136
- EUSTON_LOG.info border
137
- EUSTON_LOG.info ''
105
+ @logger.info ''
106
+ @logger.info border
107
+ @logger.info banner
108
+ @logger.info border
109
+ @logger.info ''
138
110
  end
139
111
 
140
- def trap_exit_signals daemon
141
- signals = %W(INT TERM) & Signal.list.keys
142
- signals.each { |sig| sig.freeze }.freeze
143
- signals.each { |sig| Signal.trap(sig) { daemon.queue.push(sig) } }
112
+ def remove_pid_file
113
+ @logger.debug "Deleting pid file at #{@pid_file}"
114
+
115
+ File.delete @pid_file rescue Errno::ENOENT
116
+ end
117
+
118
+ def validate_environment environment
119
+ environment = environment.to_s.downcase.to_sym
120
+ environments = [:development, :test, :staging, :production]
121
+ environment = :development unless environments.include? environment
122
+ environment
123
+ end
124
+
125
+ def write_pid_file
126
+ pid_path = @data[:daemon_config][:pid_path]
127
+
128
+ raise "Required pid path does not exist: #{pid_path}" unless Dir.exist? pid_path
129
+
130
+ @pid_file = File.join pid_path, "#{@name}.#{@environment}.pid"
131
+
132
+ if defined? Java
133
+ @pid = java.lang.management.ManagementFactory.getRuntimeMXBean().getName().split('@').first
134
+ else
135
+ @pid = File.readlink("/proc/self")
136
+ end
137
+
138
+ @logger.error "Writing pid #{@pid} to #{@pid_file}"
139
+
140
+ File.open(@pid_file, 'w') { |f| f.puts @pid }
144
141
  end
145
142
  end
146
143
  end