rflow 1.0.0a1 → 1.0.0a2

Sign up to get free protection for your applications and to get access to all the features.
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