rflow 1.0.0a1 → 1.0.0a2

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +0 -1
  5. data/NOTES +0 -13
  6. data/README.md +6 -1
  7. data/bin/rflow +2 -9
  8. data/example/basic_config.rb +1 -33
  9. data/example/basic_extensions.rb +0 -98
  10. data/example/http_config.rb +2 -3
  11. data/example/http_extensions.rb +6 -63
  12. data/lib/rflow.rb +31 -39
  13. data/lib/rflow/child_process.rb +112 -0
  14. data/lib/rflow/component.rb +77 -148
  15. data/lib/rflow/component/port.rb +38 -41
  16. data/lib/rflow/components.rb +4 -8
  17. data/lib/rflow/components/clock.rb +49 -0
  18. data/lib/rflow/components/integer.rb +39 -0
  19. data/lib/rflow/components/raw.rb +10 -6
  20. data/lib/rflow/components/replicate.rb +20 -0
  21. data/lib/rflow/components/ruby_proc_filter.rb +27 -0
  22. data/lib/rflow/configuration.rb +105 -184
  23. data/lib/rflow/configuration/component.rb +1 -4
  24. data/lib/rflow/configuration/connection.rb +11 -16
  25. data/lib/rflow/configuration/port.rb +3 -5
  26. data/lib/rflow/configuration/ruby_dsl.rb +105 -119
  27. data/lib/rflow/configuration/setting.rb +19 -25
  28. data/lib/rflow/configuration/shard.rb +1 -3
  29. data/lib/rflow/connection.rb +47 -10
  30. data/lib/rflow/connections.rb +0 -1
  31. data/lib/rflow/connections/zmq_connection.rb +34 -38
  32. data/lib/rflow/daemon_process.rb +155 -0
  33. data/lib/rflow/logger.rb +41 -25
  34. data/lib/rflow/master.rb +23 -105
  35. data/lib/rflow/message.rb +78 -108
  36. data/lib/rflow/pid_file.rb +37 -37
  37. data/lib/rflow/shard.rb +33 -100
  38. data/lib/rflow/version.rb +2 -2
  39. data/rflow.gemspec +2 -2
  40. data/schema/tick.avsc +10 -0
  41. data/spec/fixtures/config_ints.rb +4 -40
  42. data/spec/fixtures/config_shards.rb +1 -2
  43. data/spec/fixtures/extensions_ints.rb +0 -98
  44. data/spec/rflow/component/port_spec.rb +61 -0
  45. data/spec/rflow/components/clock_spec.rb +72 -0
  46. data/spec/rflow/configuration/ruby_dsl_spec.rb +150 -0
  47. data/spec/rflow/configuration_spec.rb +54 -0
  48. data/spec/rflow/forward_to_input_port_spec.rb +48 -0
  49. data/spec/rflow/forward_to_output_port_spec.rb +40 -0
  50. data/spec/rflow/logger_spec.rb +48 -0
  51. data/spec/rflow/message/data/raw_spec.rb +29 -0
  52. data/spec/rflow/message/data_spec.rb +58 -0
  53. data/spec/rflow/message_spec.rb +154 -0
  54. data/spec/rflow_spec.rb +94 -124
  55. data/spec/spec_helper.rb +8 -12
  56. metadata +46 -22
  57. data/lib/rflow/components/raw/extensions.rb +0 -18
  58. data/lib/rflow/port.rb +0 -4
  59. data/lib/rflow/util.rb +0 -19
  60. data/spec/rflow_component_port_spec.rb +0 -58
  61. data/spec/rflow_configuration_ruby_dsl_spec.rb +0 -148
  62. data/spec/rflow_configuration_spec.rb +0 -73
  63. data/spec/rflow_message_data_raw.rb +0 -26
  64. data/spec/rflow_message_data_spec.rb +0 -60
  65. data/spec/rflow_message_spec.rb +0 -182
  66. data/spec/schema_spec.rb +0 -28
  67. data/temp.rb +0 -295
data/lib/rflow/master.rb CHANGED
@@ -1,127 +1,45 @@
1
+ require 'rflow/daemon_process'
1
2
  require 'rflow/pid_file'
2
3
  require 'rflow/shard'
3
4
 
4
5
  class RFlow
5
- class Master
6
-
7
- attr_accessor :name, :pid_file, :ready_write
8
- attr_accessor :shards
6
+ class Master < DaemonProcess
7
+ attr_reader :shards
9
8
 
10
9
  def initialize(config)
11
- @name = config['rflow.application_name']
10
+ super(config['rflow.application_name'], 'Master')
12
11
  @pid_file = PIDFile.new(config['rflow.pid_file_path'])
13
- @shards = config.shards.map do |shard_config|
14
- Shard.new(shard_config)
15
- end
12
+ @shards = config.shards.map {|config| Shard.new(config) }
16
13
  end
17
14
 
18
- def handle_signals
19
- # Gracefully shutdown on termination signals
20
- ['SIGTERM', 'SIGINT', 'SIGQUIT', 'SIGCHLD'].each do |signal|
21
- Signal.trap signal do
22
- # Log4r and traps don't mix, so we need to put it in another thread
23
- Thread.new { shutdown(signal) }.join
24
- end
25
- end
26
-
27
- # Reopen logs on USR1
28
- ['SIGUSR1'].each do |signal|
29
- Signal.trap signal do
30
- Thread.new do
31
- RFlow.logger.reopen
32
- signal_workers(signal)
33
- end.join
34
- end
35
- end
36
-
37
- # Toggle log level on USR2
38
- ['SIGUSR2'].each do |signal|
39
- Signal.trap signal do
40
- Thread.new do
41
- RFlow.logger.toggle_log_level
42
- signal_workers(signal)
43
- end.join
44
- end
45
- end
15
+ def run!
16
+ write_pid_file
17
+ super
18
+ ensure
19
+ remove_pid_file
46
20
  end
47
21
 
48
- def run
49
- Log4r::NDC.clear
50
- Log4r::NDC.push name
51
- $0 = name
52
-
53
- shards.each {|s| s.run!}
54
-
55
- handle_signals
56
-
57
- # Signal the grandparent that we are running
58
- if ready_write
59
- ready_write.syswrite($$.to_s)
60
- ready_write.close rescue nil
61
- end
62
-
63
- pid_file.write
64
-
65
- RFlow.logger.info "Master started"
66
-
67
- EM.run do
68
- # TODO: Monitor the workers
69
- end
70
-
71
- @pid_file.safe_unlink
22
+ def spawn_subprocesses
23
+ shards.each(&:run!)
72
24
  end
73
25
 
74
- def daemonize!
75
- RFlow.logger.info "#{name} daemonizing"
76
-
77
- ready_read, @ready_write = IO.pipe
78
- [ready_read, @ready_write].each { |io| io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
79
-
80
- grandparent = $$
81
-
82
- if fork
83
- # Grandparent waits for a PID on the pipe indicating that the
84
- # master successfully started.
85
- @ready_write.close # grandparent does not write
86
- master_pid = (ready_read.readpartial(16) rescue nil).to_i
87
- unless master_pid > 1
88
- RFlow.logger.error "Master failed to start"
89
- exit! 1
90
- end
91
- RFlow.logger.info "Master indicated successful daemonization"
92
- exit 0
93
- end
94
-
95
- Process.daemon(true, true)
96
-
97
- ready_read.close # master does not read
98
-
99
- # Close standard IO
100
- $stdout.sync = $stderr.sync = true
101
- $stdin.binmode; $stdout.binmode; $stderr.binmode
102
- begin; $stdin.reopen "/dev/null"; rescue ::Exception; end
103
- begin; $stdout.reopen "/dev/null"; rescue ::Exception; end
104
- begin; $stderr.reopen "/dev/null"; rescue ::Exception; end
105
-
106
- $$
26
+ def subprocesses
27
+ shards.flat_map(&:workers)
107
28
  end
108
29
 
109
- def signal_workers(signal)
110
- shards.each do |shard|
111
- shard.workers.each do |worker|
112
- RFlow.logger.info "Signalling #{worker.name} with #{signal}"
113
- Process.kill(signal, worker.pid)
114
- end
30
+ def run_process
31
+ EM.run do
32
+ # TODO: Monitor the workers
115
33
  end
116
34
  end
117
35
 
118
- def shutdown(reason)
119
- RFlow.logger.info "#{name} shutting down due to #{reason}"
120
- signal_workers('QUIT')
121
- pid_file.safe_unlink
122
- RFlow.logger.info "#{name} exiting"
123
- exit 0
36
+ def shutdown!(reason)
37
+ remove_pid_file
38
+ super
124
39
  end
125
40
 
41
+ private
42
+ def write_pid_file; @pid_file.write; end
43
+ def remove_pid_file; @pid_file.safe_unlink; end
126
44
  end
127
45
  end
data/lib/rflow/message.rb CHANGED
@@ -1,127 +1,108 @@
1
1
  require 'stringio'
2
2
  require 'time'
3
-
4
3
  require 'avro'
5
-
6
4
  require 'rflow/configuration'
7
5
 
8
6
  class RFlow
7
+ class Avro
8
+ def self.decode(reader, bytes)
9
+ reader.read ::Avro::IO::BinaryDecoder.new(StringIO.new(bytes.force_encoding('BINARY')))
10
+ end
9
11
 
10
- # TODO: reduce reliance/expectation on avro serialization in method
11
- # names and such.
12
- class Message
12
+ def self.encode(writer, message)
13
+ String.new.force_encoding('BINARY').tap do |result|
14
+ writer.write message, ::Avro::IO::BinaryEncoder.new(StringIO.new(result, 'w'))
15
+ end
16
+ end
17
+ end
13
18
 
19
+ class Message
14
20
  class << self
15
- def avro_message_schema; @avro_message_schema ||= Avro::Schema.parse(File.read(File.join(File.dirname(__FILE__), '..', '..', 'schema', 'message.avsc'))); end
16
-
17
- def avro_reader; @avro_reader ||= Avro::IO::DatumReader.new(avro_message_schema, avro_message_schema); end
18
- def avro_writer; @avro_writer ||= Avro::IO::DatumWriter.new(avro_message_schema); end
19
- def avro_decoder(io_object); Avro::IO::BinaryDecoder.new(io_object); end
20
- def avro_encoder(io_object); Avro::IO::BinaryEncoder.new(io_object); end
21
+ def schema; @schema ||= ::Avro::Schema.parse(File.read(File.join(File.dirname(__FILE__), '..', '..', 'schema', 'message.avsc'))); end
22
+ def message_reader; @message_reader ||= ::Avro::IO::DatumReader.new(schema, schema); end
23
+ def message_writer; @message_writer ||= ::Avro::IO::DatumWriter.new(schema); end
24
+ def encode(message); RFlow::Avro.encode(message_writer, message); end
21
25
 
22
26
  # Take in an Avro serialization of a message and return a new
23
27
  # Message object. Assumes the org.rflow.Message Avro schema.
24
- def from_avro(avro_serialized_message_byte_string)
25
- avro_serialized_message_byte_stringio = StringIO.new(avro_serialized_message_byte_string)
26
- message_hash = avro_reader.read avro_decoder(avro_serialized_message_byte_stringio)
27
- Message.new(message_hash['data_type_name'], message_hash['provenance'],
28
- message_hash['data_serialization_type'], message_hash['data_schema'],
29
- message_hash['data'])
28
+ def from_avro(bytes)
29
+ message = RFlow::Avro.decode(message_reader, bytes)
30
+ Message.new(message['data_type_name'], message['provenance'],
31
+ message['data_serialization_type'], message['data_schema'],
32
+ message['data'])
30
33
  end
31
34
  end
32
35
 
33
-
34
- # Serialize the current message object to Avro using the
35
- # org.rflow.Message Avro schema. Note that we have to manually
36
- # set the encoding for Ruby 1.9, otherwise the stringio would use
37
- # UTF-8 by default, which would not work correctly, as a serialize
38
- # avro string is BINARY, not UTF-8
39
- def to_avro
40
- avro_serialized_message_bytes = ''
41
- avro_serialized_message_bytes.force_encoding 'BINARY'
42
- avro_serialized_message_bytes_stringio = StringIO.new(avro_serialized_message_bytes, 'w')
43
-
44
- deserialized_avro_object = {
45
- 'data_type_name' => self.data_type_name.to_s,
46
- 'provenance' => self.provenance.map(&:to_hash),
47
- 'data_serialization_type' => self.data.serialization_type.to_s,
48
- 'data_schema' => self.data.schema_string,
49
- 'data' => self.data.to_avro
50
- }
51
-
52
- self.class.avro_writer.write deserialized_avro_object, self.class.avro_encoder(avro_serialized_message_bytes_stringio)
53
- avro_serialized_message_bytes
54
- end
55
-
56
-
57
- attr_reader :data_type_name
58
- attr_accessor :processing_event
36
+ attr_reader :data_type_name, :data
59
37
  attr_accessor :provenance
60
- attr_reader :data, :data_extensions
61
38
 
62
-
63
- def initialize(data_type_name, provenance=[], data_serialization_type='avro', data_schema_string=nil, serialized_data_object=nil)
64
- # Default the values, in case someone puts in a nil instead of
65
- # the default
39
+ def initialize(data_type_name, provenance = [], serialization_type = 'avro', schema = nil, serialized_data = nil)
66
40
  @data_type_name = data_type_name.to_s
67
41
 
68
- # Turn the provenance array of Hashes into an array of
69
- # ProcessingEvents for easier access and time validation. TODO:
70
- # do this lazily so as not to create/destroy objects that are
71
- # never used
72
- @provenance = (provenance || []).map do |processing_event_hash_or_object|
73
- if processing_event_hash_or_object.is_a? ProcessingEvent
74
- processing_event_hash_or_object
75
- else
76
- ProcessingEvent.new(processing_event_hash_or_object['component_instance_uuid'],
77
- processing_event_hash_or_object['started_at'],
78
- processing_event_hash_or_object['completed_at'],
79
- processing_event_hash_or_object['context'])
80
- end
81
- end
82
-
83
42
  # TODO: Make this better. This check is technically
84
- # unnecessary, as we are able to completely desrialize the
43
+ # unnecessary, as we are able to completely deserialize the
85
44
  # message without needing to resort to the registered schema.
86
- registered_data_schema_string = RFlow::Configuration.available_data_types[@data_type_name][data_serialization_type.to_s]
87
- unless registered_data_schema_string
88
- error_message = "Data type '#{@data_type_name}' with serialization_type '#{data_serialization_type}' not found"
89
- RFlow.logger.error error_message
90
- raise ArgumentError, error_message
45
+ registered_schema = RFlow::Configuration.available_data_types[@data_type_name][serialization_type.to_s]
46
+ unless registered_schema
47
+ raise ArgumentError, "Data type '#{@data_type_name}' with serialization_type '#{serialization_type}' not found"
91
48
  end
92
49
 
93
50
  # TODO: think about registering the schemas automatically if not
94
51
  # found in Configuration
95
- if data_schema_string && (registered_data_schema_string != data_schema_string)
96
- error_message = "Passed schema ('#{data_schema_string}') does not equal registered schema ('#{registered_data_schema_string}') for data type '#{@data_type_name}' with serialization_type '#{data_serialization_type}'"
97
- RFlow.logger.error error_message
98
- raise ArgumentError, error_message
52
+ if schema && (registered_schema != schema)
53
+ raise ArgumentError, "Passed schema ('#{schema}') does not equal registered schema ('#{registered_schema}') for data type '#{@data_type_name}' with serialization_type '#{serialization_type}'"
54
+ end
55
+
56
+ # Turn the provenance array of Hashes into an array of
57
+ # ProcessingEvents for easier access and time validation.
58
+ # TODO: do this lazily so as not to create/destroy objects that are
59
+ # never used
60
+ @provenance = (provenance || []).map do |event|
61
+ if event.is_a? ProcessingEvent
62
+ event
63
+ else
64
+ ProcessingEvent.new(event['component_instance_uuid'],
65
+ event['started_at'], event['completed_at'],
66
+ event['context'])
67
+ end
99
68
  end
100
69
 
101
- @data = Data.new(registered_data_schema_string, data_serialization_type.to_s, serialized_data_object)
70
+ @data = Data.new(registered_schema, serialization_type.to_s, serialized_data)
102
71
 
103
72
  # Get the extensions and apply them to the data object to add capability
104
- @data_extensions = RFlow::Configuration.available_data_extensions[@data_type_name]
105
- @data_extensions.each do |data_extension|
106
- RFlow.logger.debug "Extending '#{data_type_name}' with extension '#{data_extension}'"
107
- @data.extend data_extension
73
+ RFlow::Configuration.available_data_extensions[@data_type_name].each do |e|
74
+ RFlow.logger.debug "Extending '#{data_type_name}' with extension '#{e}'"
75
+ @data.extend e
108
76
  end
109
77
  end
110
78
 
79
+ # Serialize the current message object to Avro using the
80
+ # org.rflow.Message Avro schema. Note that we have to manually
81
+ # set the encoding for Ruby 1.9, otherwise the stringio would use
82
+ # UTF-8 by default, which would not work correctly, as a serialize
83
+ # avro string is BINARY, not UTF-8
84
+ def to_avro
85
+ Message.encode('data_type_name' => data_type_name.to_s,
86
+ 'provenance' => provenance.map(&:to_hash),
87
+ 'data_serialization_type' => data.serialization_type.to_s,
88
+ 'data_schema' => data.schema_string,
89
+ 'data' => data.to_avro)
90
+ end
111
91
 
112
92
  class ProcessingEvent
113
- attr_accessor :component_instance_uuid, :started_at, :completed_at, :context
93
+ attr_reader :component_instance_uuid, :started_at
94
+ attr_accessor :completed_at, :context
114
95
 
115
- def initialize(component_instance_uuid, started_at=nil, completed_at=nil, context=nil)
96
+ def initialize(component_instance_uuid, started_at = nil, completed_at = nil, context = nil)
116
97
  @component_instance_uuid = component_instance_uuid
117
98
  @started_at = case started_at
118
- when String then Time.xmlschema(started_at)
119
- when Time then started_at
120
- else; nil; end
99
+ when String; Time.xmlschema(started_at)
100
+ when Time; started_at
101
+ else nil; end
121
102
  @completed_at = case completed_at
122
- when String then Time.xmlschema(completed_at)
123
- when Time then completed_at
124
- else; nil; end
103
+ when String; Time.xmlschema(completed_at)
104
+ when Time; completed_at
105
+ else nil; end
125
106
  @context = context
126
107
  end
127
108
 
@@ -142,41 +123,31 @@ class RFlow
142
123
  attr_reader :schema_string, :schema, :serialization_type
143
124
  attr_accessor :data_object
144
125
 
145
- def initialize(schema_string, serialization_type='avro', serialized_data_object=nil)
146
- unless serialization_type == 'avro'
147
- error_message = "Only Avro serialization_type supported at the moment"
148
- RFlow.logger.error error_message
149
- raise ArgumentError, error_message
150
- end
126
+ def initialize(schema_string, serialization_type = 'avro', serialized_data = nil)
127
+ raise ArgumentError, "Only Avro serialization_type supported at the moment" unless serialization_type.to_s == 'avro'
151
128
 
152
129
  @schema_string = schema_string
153
- @serialization_type = serialization_type
130
+ @serialization_type = serialization_type.to_s
154
131
 
155
132
  begin
156
- @schema = Avro::Schema.parse(schema_string)
133
+ @schema = ::Avro::Schema.parse(schema_string)
134
+ @writer = ::Avro::IO::DatumWriter.new(@schema)
157
135
  rescue Exception => e
158
- error_message = "Invalid schema '#{@schema_string}': #{e}: #{e.message}"
159
- RFlow.logger.error error_message
160
- raise ArgumentError, error_message
136
+ raise ArgumentError, "Invalid schema '#{@schema_string}': #{e}: #{e.message}"
161
137
  end
162
138
 
163
- if serialized_data_object
164
- serialized_data_object.force_encoding 'BINARY'
165
- avro_decoder = Avro::IO::BinaryDecoder.new StringIO.new(serialized_data_object)
166
- @data_object = Avro::IO::DatumReader.new(schema, schema).read avro_decoder
139
+ if serialized_data
140
+ serialized_data.force_encoding 'BINARY'
141
+ @data_object = RFlow::Avro.decode(::Avro::IO::DatumReader.new(schema, schema), serialized_data)
167
142
  end
168
143
  end
169
144
 
170
145
  def valid?
171
- Avro::Schema.validate @schema, @data_object
146
+ ::Avro::Schema.validate @schema, @data_object
172
147
  end
173
148
 
174
149
  def to_avro
175
- serialized_data_object_bytes = ''
176
- serialized_data_object_bytes.force_encoding 'BINARY'
177
- serialized_data_object_bytes_stringio = StringIO.new(serialized_data_object_bytes)
178
- Avro::IO::DatumWriter.new(@schema).write @data_object, Avro::IO::BinaryEncoder.new(serialized_data_object_bytes_stringio)
179
- serialized_data_object_bytes
150
+ RFlow::Avro.encode @writer, @data_object
180
151
  end
181
152
 
182
153
  # Proxy methods down to the underlying data_object, probably a
@@ -186,6 +157,5 @@ class RFlow
186
157
  @data_object.send(method_sym, *args, &block)
187
158
  end
188
159
  end
189
-
190
160
  end
191
161
  end
@@ -1,52 +1,35 @@
1
1
  class RFlow
2
2
  class PIDFile
3
- attr_reader :pid_file_path
3
+ private
4
+ attr_reader :path
4
5
 
5
- def initialize(pid_file_path)
6
- @pid_file_path = pid_file_path
7
- end
8
-
9
- def validate?
10
- if current_process?
11
- RFlow.logger.warn "Already running #{read.to_s}, not writing PID to file '#{to_s}'"
12
- return nil
13
- elsif running?
14
- error_message = "Already running #{read.to_s}, possibly stale PID file '#{to_s}'"
15
- RFlow.logger.error error_message
16
- raise ArgumentError, error_message
17
- elsif exist?
18
- RFlow.logger.warn "Found stale PID #{read.to_s} in PID file '#{to_s}', removing"
19
- unlink
20
- end
21
- true
6
+ public
7
+ def initialize(path)
8
+ @path = path
22
9
  end
23
10
 
24
11
  def read
25
- return nil unless File.exist? pid_file_path
26
- File.read(pid_file_path).to_i
12
+ return nil unless File.exist? path
13
+ File.read(path).to_i
27
14
  end
28
15
 
29
- def write(pid=$$)
16
+ def write(pid = $$)
30
17
  return unless validate?
31
18
 
32
19
  RFlow.logger.debug "Writing PID #{pid} file '#{to_s}'"
33
20
  pid_fp = begin
34
- tmp_pid_file_path = File.join(File.dirname(pid_file_path), ".#{File.basename(pid_file_path)}")
35
- File.open(tmp_pid_file_path, File::RDWR|File::CREAT|File::EXCL, 0644)
21
+ tmp_path = File.join(File.dirname(path), ".#{File.basename(path)}")
22
+ File.open(tmp_path, File::RDWR|File::CREAT|File::EXCL, 0644)
36
23
  rescue Errno::EEXIST
37
24
  retry
38
25
  end
39
26
  pid_fp.syswrite("#{pid}\n")
40
- File.rename(pid_fp.path, pid_file_path)
27
+ File.rename(pid_fp.path, path)
41
28
  pid_fp.close
42
29
 
43
30
  pid
44
31
  end
45
32
 
46
- def exist?
47
- File.exist? pid_file_path
48
- end
49
-
50
33
  def running?
51
34
  return false unless exist?
52
35
  pid = read
@@ -57,14 +40,6 @@ class RFlow
57
40
  nil
58
41
  end
59
42
 
60
- def current_process?
61
- read == $$
62
- end
63
-
64
- def unlink
65
- File.unlink(pid_file_path)
66
- end
67
-
68
43
  # unlinks a PID file at given if it contains the current PID still
69
44
  # potentially racy without locking the directory (which is
70
45
  # non-portable and may interact badly with other programs), but the
@@ -78,7 +53,32 @@ class RFlow
78
53
  end
79
54
 
80
55
  def to_s
81
- File.expand_path(pid_file_path)
56
+ File.expand_path(path)
57
+ end
58
+
59
+ private
60
+ def validate?
61
+ if current_process?
62
+ return nil
63
+ elsif running?
64
+ raise ArgumentError, "Process #{read.to_s} referenced in stale PID file '#{to_s}' still exists; probably attempting to run duplicate RFlow instances"
65
+ elsif exist?
66
+ RFlow.logger.warn "Found stale PID #{read.to_s} in PID file '#{to_s}', removing"
67
+ unlink
68
+ end
69
+ true
70
+ end
71
+
72
+ def exist?
73
+ File.exist? path
74
+ end
75
+
76
+ def current_process?
77
+ read == $$
78
+ end
79
+
80
+ def unlink
81
+ File.unlink(path)
82
82
  end
83
83
  end
84
84
  end