rtext 0.8.0 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +113 -84
  3. data/Project.yaml +14 -0
  4. data/RText_Protocol +47 -4
  5. data/lib/rtext/context_builder.rb +49 -8
  6. data/lib/rtext/default_completer.rb +212 -163
  7. data/lib/rtext/default_service_provider.rb +3 -3
  8. data/lib/rtext/frontend/connector.rb +122 -55
  9. data/lib/rtext/frontend/context.rb +12 -12
  10. data/lib/rtext/instantiator.rb +11 -3
  11. data/lib/rtext/language.rb +5 -5
  12. data/lib/rtext/serializer.rb +1 -1
  13. data/lib/rtext/service.rb +264 -253
  14. data/lib/rtext/tokenizer.rb +1 -1
  15. metadata +18 -43
  16. data/Rakefile +0 -46
  17. data/test/completer_test.rb +0 -606
  18. data/test/context_builder_test.rb +0 -948
  19. data/test/frontend/context_test.rb +0 -205
  20. data/test/instantiator_test.rb +0 -1691
  21. data/test/integration/backend.out +0 -13
  22. data/test/integration/crash_on_request_editor.rb +0 -12
  23. data/test/integration/ecore_editor.rb +0 -50
  24. data/test/integration/frontend.log +0 -36049
  25. data/test/integration/model/invalid_encoding.invenc +0 -2
  26. data/test/integration/model/test.crash_on_request +0 -18
  27. data/test/integration/model/test.crashing_backend +0 -18
  28. data/test/integration/model/test.dont_open_socket +0 -0
  29. data/test/integration/model/test.invalid_cmd_line +0 -0
  30. data/test/integration/model/test.not_in_rtext +0 -0
  31. data/test/integration/model/test_large_with_errors.ect3 +0 -43523
  32. data/test/integration/model/test_metamodel.ect +0 -24
  33. data/test/integration/model/test_metamodel2.ect +0 -5
  34. data/test/integration/model/test_metamodel_error.ect2 +0 -3
  35. data/test/integration/model/test_metamodel_ok.ect2 +0 -18
  36. data/test/integration/test.rb +0 -918
  37. data/test/link_detector_test.rb +0 -287
  38. data/test/message_helper_test.rb +0 -118
  39. data/test/rtext_test.rb +0 -11
  40. data/test/serializer_test.rb +0 -1004
  41. data/test/tokenizer_test.rb +0 -173
@@ -1,163 +1,212 @@
1
- require 'rgen/ecore/ecore_ext'
2
-
3
- module RText
4
-
5
- class DefaultCompleter
6
-
7
- CompletionOption = Struct.new(:text, :extra)
8
-
9
- # Creates a completer for RText::Language +language+.
10
- #
11
- def initialize(language)
12
- @lang = language
13
- end
14
-
15
- # Provides completion options
16
- #
17
- def complete(context)
18
- clazz = context && context.element && context.element.class.ecore
19
- if clazz
20
- if context.in_block
21
- block_options(context, clazz)
22
- elsif !context.problem
23
- result = []
24
- if context.feature
25
- add_value_options(context, result)
26
- end
27
- if !context.after_label
28
- add_label_options(context, clazz, result)
29
- end
30
- result
31
- else
32
- # missing comma, after curly brace, etc.
33
- []
34
- end
35
- elsif context
36
- root_options
37
- else
38
- []
39
- end
40
- end
41
-
42
- def block_options(context, clazz)
43
- types = []
44
- labled_refs = []
45
- if context.feature
46
- if context.feature.is_a?(RGen::ECore::EReference) && context.feature.containment
47
- types = @lang.concrete_types(context.feature.eType)
48
- else
49
- # invalid, ignore
50
- end
51
- else
52
- # all target types which don't need a label
53
- # and all lables which are needed by a potential target type
54
- @lang.containments(clazz).each do |r|
55
- ([r.eType] + r.eType.eAllSubTypes).select{|t| !t.abstract}.each do |t|
56
- if @lang.labeled_containment?(clazz, r) || @lang.containments_by_target_type(clazz, t).size > 1
57
- labled_refs << r
58
- else
59
- types << t
60
- end
61
- end
62
- end
63
- end
64
- types.uniq.
65
- sort{|a,b| a.name <=> b.name}.collect do |c|
66
- class_completion_option(c)
67
- end +
68
- labled_refs.uniq.collect do |r|
69
- CompletionOption.new("#{r.name}:", "<#{r.eType.name}>")
70
- end
71
- end
72
-
73
- def add_value_options(context, result)
74
- if context.feature.is_a?(RGen::ECore::EAttribute) || !context.feature.containment
75
- if context.feature.is_a?(RGen::ECore::EReference)
76
- result.concat(reference_options(context))
77
- elsif context.feature.eType.is_a?(RGen::ECore::EEnum)
78
- result.concat(enum_options(context))
79
- elsif context.feature.eType.instanceClass == String
80
- result.concat(string_options(context))
81
- elsif context.feature.eType.instanceClass == Integer
82
- result.concat(integer_options(context))
83
- elsif context.feature.eType.instanceClass == Float
84
- result.concat(float_options(context))
85
- elsif context.feature.eType.instanceClass == RGen::MetamodelBuilder::DataTypes::Boolean
86
- result.concat(boolean_options(context))
87
- else
88
- # no options
89
- end
90
- else
91
- # containment reference, ignore
92
- end
93
- end
94
-
95
- def add_label_options(context, clazz, result)
96
- result.concat(@lang.labled_arguments(clazz).
97
- select{|f|
98
- !context.element.eIsSet(f.name)}.collect do |f|
99
- CompletionOption.new("#{f.name}:", "<#{f.eType.name}>")
100
- end )
101
- end
102
-
103
- def root_options
104
- @lang.root_classes.
105
- sort{|a,b| a.name <=> b.name}.collect do |c|
106
- class_completion_option(c)
107
- end
108
- end
109
-
110
- def reference_options(context)
111
- []
112
- end
113
-
114
- def enum_options(context)
115
- context.feature.eType.eLiterals.collect do |l|
116
- lname = l.name
117
- if lname =~ /^\d|\W/ || lname == "true" || lname == "false"
118
- lname = "\"#{lname.gsub("\\","\\\\\\\\").gsub("\"","\\\"").gsub("\n","\\n").
119
- gsub("\r","\\r").gsub("\t","\\t").gsub("\f","\\f").gsub("\b","\\b")}\""
120
- end
121
- CompletionOption.new("#{lname}", "<#{context.feature.eType.name}>")
122
- end
123
- end
124
-
125
- def string_options(context)
126
- if @lang.unquoted?(context.feature)
127
- [ CompletionOption.new("#{context.feature.name.gsub(/\W/,"")}", value_description(context)) ]
128
- else
129
- [ CompletionOption.new("\"\"", value_description(context)) ]
130
- end
131
- end
132
-
133
- def integer_options(context)
134
- (0..0).collect{|i| CompletionOption.new("#{i}", value_description(context)) }
135
- end
136
-
137
- def float_options(context)
138
- (0..0).collect{|i| CompletionOption.new("#{i}.0", value_description(context)) }
139
- end
140
-
141
- def boolean_options(context)
142
- [true, false].collect{|b| CompletionOption.new("#{b}", value_description(context)) }
143
- end
144
-
145
- private
146
-
147
- def value_description(context)
148
- if context.after_label
149
- "<#{context.feature.eType.name}>"
150
- else
151
- "[#{context.feature.name}] <#{context.feature.eType.name}>"
152
- end
153
- end
154
-
155
- def class_completion_option(eclass)
156
- uargs = @lang.unlabled_arguments(eclass).collect{|a| "<#{a.name}>"}.join(", ")
157
- CompletionOption.new(@lang.command_by_class(eclass.instanceClass), uargs)
158
- end
159
-
160
- end
161
-
162
- end
163
-
1
+ require 'rgen/ecore/ecore_ext'
2
+
3
+ module RText
4
+
5
+ class DefaultCompleter
6
+
7
+ class CompletionOption
8
+
9
+ attr_accessor :insert
10
+ attr_accessor :display
11
+ attr_accessor :extra
12
+ attr_accessor :description
13
+
14
+ def self.from_text_extra(text, extra)
15
+ self.new(text, text + ' ' + extra, nil, extra)
16
+ end
17
+
18
+ def self.for_curly_braces(context)
19
+ self.new("{\n#{context.line_indent}#{context.indent}||\n#{context.line_indent}}", '{}')
20
+ end
21
+
22
+ def self.for_square_brackets
23
+ self.new('[ || ]', '[]', '')
24
+ end
25
+
26
+ def initialize(insert, display, description=nil, extra=nil)
27
+ @insert = insert
28
+ @display = display
29
+ @description = description
30
+ @extra = extra
31
+ end
32
+
33
+ def text
34
+ @insert
35
+ end
36
+
37
+ end
38
+
39
+ # Creates a completer for RText::Language +language+.
40
+ #
41
+ def initialize(language)
42
+ @lang = language
43
+ end
44
+
45
+ # Provides completion options
46
+ #
47
+ def complete(context, version=0)
48
+ clazz = context && context.element && context.element.class.ecore
49
+ if clazz
50
+ if context.position.in_block
51
+ block_options(context, clazz)
52
+ elsif !context.problem
53
+ result = []
54
+ add_value_options(context, result, version) if context.feature
55
+ add_label_options(context, clazz, result, version) unless context.position.after_label
56
+ result
57
+ else
58
+ # missing comma, after curly brace, etc.
59
+ if version > 0 && !context.position.before_brace &&
60
+ context.element.class.ecore.eAllReferences.any? { |r| r.containment }
61
+ [CompletionOption.for_curly_braces(context)]
62
+ else
63
+ []
64
+ end
65
+ end
66
+ elsif context
67
+ root_options
68
+ else
69
+ []
70
+ end
71
+ end
72
+
73
+ def block_options(context, clazz)
74
+ types = []
75
+ labled_refs = []
76
+ if context.feature
77
+ if context.feature.is_a?(RGen::ECore::EReference) && context.feature.containment
78
+ types = @lang.concrete_types(context.feature.eType)
79
+ else
80
+ # invalid, ignore
81
+ end
82
+ else
83
+ # all target types which don't need a label
84
+ # and all lables which are needed by a potential target type
85
+ @lang.containments(clazz).each do |r|
86
+ ([r.eType] + r.eType.eAllSubTypes).select{|t| t.concrete}.each do |t|
87
+ if @lang.labeled_containment?(clazz, r) || @lang.containments_by_target_type(clazz, t).size > 1
88
+ labled_refs << r
89
+ else
90
+ types << t
91
+ end
92
+ end
93
+ end
94
+ end
95
+ types.uniq.
96
+ sort{|a,b| a.name <=> b.name}.collect do |c|
97
+ class_completion_option(c)
98
+ end +
99
+ labled_refs.uniq.collect do |r|
100
+ CompletionOption.from_text_extra("#{r.name}:", "<#{r.eType.name}>")
101
+ end
102
+ end
103
+
104
+ def add_value_options(context, result, version)
105
+ if context.feature.is_a?(RGen::ECore::EAttribute) || !context.feature.containment
106
+ if context.feature.is_a?(RGen::ECore::EReference)
107
+ result.concat(reference_options(context))
108
+ if version > 0 && !context.position.before_bracket && context.feature.upperBound != 1
109
+ result << CompletionOption.for_square_brackets
110
+ end
111
+ elsif context.feature.eType.is_a?(RGen::ECore::EEnum)
112
+ result.concat(enum_options(context))
113
+ elsif context.feature.eType.instanceClass == String
114
+ result.concat(string_options(context))
115
+ elsif context.feature.eType.instanceClass == Integer
116
+ result.concat(integer_options(context))
117
+ elsif context.feature.eType.instanceClass == Float
118
+ result.concat(float_options(context))
119
+ elsif context.feature.eType.instanceClass == RGen::MetamodelBuilder::DataTypes::Boolean
120
+ result.concat(boolean_options(context))
121
+ else
122
+ # no options
123
+ end
124
+ else
125
+ if version > 0 && !context.position.before_bracket && context.feature.upperBound != 1
126
+ result << CompletionOption.for_square_brackets
127
+ end
128
+ end
129
+ end
130
+
131
+ def add_label_options(context, clazz, result, version)
132
+ result.concat(@lang.labled_arguments(clazz).
133
+ select{|f|
134
+ !context.element.eIsSet(f.name)}.collect do |f|
135
+ CompletionOption.from_text_extra("#{f.name}:", "<#{f.eType.name}>")
136
+ end )
137
+ if version > 0 && !context.position.after_comma &&
138
+ context.element.class.ecore.eAllReferences.any? { |r| r.containment } && !context.position.before_brace
139
+ result << CompletionOption.for_curly_braces(context)
140
+ end
141
+ end
142
+
143
+ def root_options
144
+ @lang.root_classes.
145
+ sort{|a,b| a.name <=> b.name}.collect do |c|
146
+ class_completion_option(c)
147
+ end
148
+ end
149
+
150
+ def reference_options(context)
151
+ []
152
+ end
153
+
154
+ def enum_options(context)
155
+ context.feature.eType.eLiterals.collect do |l|
156
+ lname = l.name
157
+ if lname =~ /^\d|\W/ || lname == "true" || lname == "false"
158
+ lname = "\"#{lname.gsub("\\","\\\\\\\\").gsub("\"","\\\"").gsub("\n","\\n").
159
+ gsub("\r","\\r").gsub("\t","\\t").gsub("\f","\\f").gsub("\b","\\b")}\""
160
+ end
161
+ CompletionOption.from_text_extra("#{lname}", "<#{context.feature.eType.name}>")
162
+ end
163
+ end
164
+
165
+ def string_options(context)
166
+ if @lang.unquoted?(context.feature)
167
+ [ CompletionOption.from_text_extra("#{context.feature.name.gsub(/\W/,"")}", value_description(context)) ]
168
+ else
169
+ [ CompletionOption.from_text_extra("\"\"", value_description(context)) ]
170
+ end
171
+ end
172
+
173
+ def get_default_value_completion(context)
174
+ return nil unless context.feature.defaultValue
175
+ CompletionOption.from_text_extra("#{context.feature.defaultValue}", value_description(context))
176
+ end
177
+
178
+ def integer_options(context)
179
+ default_comp = get_default_value_completion(context)
180
+ return [default_comp] if default_comp
181
+ (0..0).collect{|i| CompletionOption.from_text_extra("#{i}", value_description(context)) }
182
+ end
183
+
184
+ def float_options(context)
185
+ default_comp = get_default_value_completion(context)
186
+ return [default_comp] if default_comp
187
+ (0..0).collect{|i| CompletionOption.from_text_extra("#{i}.0", value_description(context)) }
188
+ end
189
+
190
+ def boolean_options(context)
191
+ [true, false].collect{|b| CompletionOption.from_text_extra("#{b}", value_description(context)) }
192
+ end
193
+
194
+ private
195
+
196
+ def value_description(context)
197
+ if context.position.after_label
198
+ "<#{context.feature.eType.name}>"
199
+ else
200
+ "[#{context.feature.name}] <#{context.feature.eType.name}>"
201
+ end
202
+ end
203
+
204
+ def class_completion_option(eclass)
205
+ uargs = @lang.unlabled_arguments(eclass).collect{|a| "<#{a.name}>"}.join(", ")
206
+ CompletionOption.from_text_extra(@lang.command_by_class(eclass.instanceClass), uargs)
207
+ end
208
+
209
+ end
210
+
211
+ end
212
+
@@ -35,17 +35,17 @@ class DefaultServiceProvider
35
35
  end
36
36
  end
37
37
 
38
- def get_completion_options(context)
38
+ def get_completion_options(context, version=0)
39
39
  completer = RText::DefaultCompleter.new(@lang)
40
40
  class << completer
41
41
  attr_accessor :service_provider
42
42
  def reference_options(context)
43
43
  service_provider.get_reference_completion_options(context).collect {|o|
44
- DefaultCompleter::CompletionOption.new(o.identifier, "<#{o.type}>")}
44
+ DefaultCompleter::CompletionOption.from_text_extra(o.identifier, "<#{o.type}>")}
45
45
  end
46
46
  end
47
47
  completer.service_provider = self
48
- completer.complete(context)
48
+ completer.complete(context, version)
49
49
  end
50
50
 
51
51
  ReferenceCompletionOption = Struct.new(:identifier, :type)
@@ -21,15 +21,21 @@ def initialize(config, options={})
21
21
  @outfile_provider = options[:outfile_provider]
22
22
  @keep_outfile = options[:keep_outfile]
23
23
  @connection_timeout = options[:connection_timeout] || 10
24
+ @process_id = nil
24
25
  end
25
26
 
26
27
  def execute_command(obj, options={})
27
- timeout = options[:timeout] || 5
28
+ timeout = options[:timeout] || 10
28
29
  @busy = false if @busy_start_time && (Time.now > @busy_start_time + timeout)
29
30
  if @busy
30
31
  do_work
31
- :backend_busy
32
- elsif connected?
32
+ return :backend_busy
33
+ end
34
+ unless connected?
35
+ connect unless connecting?
36
+ do_work
37
+ end
38
+ if connected?
33
39
  obj["invocation_id"] = @invocation_id
34
40
  obj["type"] = "request"
35
41
  @socket.send(serialize_message(obj), 0)
@@ -66,9 +72,7 @@ def execute_command(obj, options={})
66
72
  result
67
73
  end
68
74
  else
69
- connect unless connecting?
70
- do_work
71
- :connecting
75
+ :connecting
72
76
  end
73
77
  end
74
78
 
@@ -87,23 +91,68 @@ def stop
87
91
  sleep(0.1)
88
92
  end
89
93
  end
94
+ ensure_process_cleanup(@process_id, @keep_outfile ? nil : @out_file, 10)
95
+ @process_id = nil
90
96
  end
91
97
 
92
98
  private
93
99
 
100
+ def wait_for_process_to_exit(process_id, timeout)
101
+ with_timeout timeout do
102
+ begin
103
+ waitpid(process_id, Process::WNOHANG)
104
+ process_id = nil
105
+ true
106
+ rescue Errno::ECHILD => _
107
+ false
108
+ end
109
+ end
110
+ end
111
+
112
+ def ensure_process_cleanup(process_id, out_file, timeout)
113
+ Thread.new do
114
+ begin
115
+ unless process_id.nil?
116
+ process_id = nil if wait_for_process_to_exit(process_id, timeout)
117
+ end
118
+ ensure
119
+ unless process_id.nil?
120
+ begin
121
+ Process.kill('QUIT', process_id)
122
+ rescue Errno::ESRCH => _
123
+ end
124
+ end
125
+ File.unlink(out_file) if !out_file.nil? && File.exist?(out_file)
126
+ end
127
+ end
128
+ end
129
+
130
+ def with_timeout(timeout, sleep_time = 0.1, &block)
131
+ started = Time.now
132
+ while true do
133
+ return true if block.call
134
+ if Time.now > started + timeout
135
+ return false
136
+ end
137
+ sleep(sleep_time)
138
+ end
139
+ end
140
+
141
+
94
142
  def connected?
95
- @state == :connected && backend_running?
143
+ !@process_id.nil? && @state == :read_from_socket && backend_running?
96
144
  end
97
145
 
98
146
  def connecting?
99
- @state == :connecting
147
+ !@process_id.nil? && (@state == :wait_for_file || @state == :wait_for_port)
100
148
  end
101
149
 
102
150
  def backend_running?
103
151
  if @process_id
104
152
  begin
105
- return true unless waitpid(@process_id, Process::WNOHANG)
106
- rescue Errno::ECHILD
153
+ waitpid(@process_id, Process::WNOHANG)
154
+ return true
155
+ rescue Errno::ECHILD => _
107
156
  end
108
157
  end
109
158
  false
@@ -121,66 +170,85 @@ def tempfile_name
121
170
  end
122
171
 
123
172
  def connect
124
- @state = :connecting
173
+ return if connected?
125
174
  @connect_start_time = Time.now
126
175
 
127
176
  @logger.info @config.command if @logger
128
177
 
129
178
  if @outfile_provider
130
- @out_file = @outfile_provider.call
179
+ @out_file = File.expand_path(@outfile_provider.call)
131
180
  else
132
- @out_file = tempfile_name
181
+ @out_file = File.expand_path(tempfile_name)
133
182
  end
134
- File.unlink(@out_file) if File.exist?(@out_file)
135
183
 
136
- Dir.chdir(File.dirname(@config.file)) do
137
- @process_id = spawn(@config.command.strip + " > #{@out_file} 2>&1")
184
+ if @process_id.nil?
185
+ File.unlink(@out_file) if File.exist?(@out_file)
186
+ Dir.chdir(File.dirname(@config.file)) do
187
+ @process_id = spawn(@config.command.strip + " > #{@out_file} 2>&1")
188
+ @state = :wait_for_file
189
+ end
138
190
  end
139
- @work_state = :wait_for_file
140
191
  end
141
192
 
142
193
  def do_work
143
- case @work_state
144
- when :wait_for_file
145
- if File.exist?(@out_file)
146
- @work_state = :wait_for_port
147
- end
148
- if Time.now > @connect_start_time + @connection_timeout
149
- cleanup
150
- @connection_listener.call(:timeout) if @connection_listener
151
- @work_state = :done
152
- @state = :off
153
- @logger.warn "process didn't startup (connection timeout)" if @logger
194
+ if @process_id.nil?
195
+ @state = :off
196
+ return false
197
+ end
198
+ if @state == :wait_for_port && !File.exist?(@out_file)
199
+ @state = :wait_for_file
200
+ end
201
+ if @state == :wait_for_file && File.exist?(@out_file)
202
+ @state = :wait_for_port
203
+ end
204
+ if @state == :wait_for_file
205
+ while true
206
+ if Time.now > @connect_start_time + @connection_timeout
207
+ cleanup
208
+ @connection_listener.call(:timeout) if @connection_listener
209
+ @state = :off
210
+ @logger.warn "process didn't startup (connection timeout)" if @logger
211
+ return false
212
+ end
213
+ sleep(0.1)
214
+ if File.exist?(@out_file)
215
+ @state = :wait_for_port
216
+ break
217
+ end
154
218
  end
155
- true
156
- when :wait_for_port
157
- output = File.read(@out_file)
158
- if output =~ /^RText service, listening on port (\d+)/
159
- port = $1.to_i
160
- @logger.info "connecting to #{port}" if @logger
161
- begin
162
- @socket = TCPSocket.new("127.0.0.1", port)
163
- @socket.setsockopt(:SOCKET, :RCVBUF, 1000000)
164
- rescue Errno::ECONNREFUSED
219
+ end
220
+ if @state == :wait_for_port
221
+ while true
222
+ break unless File.exist?(@out_file)
223
+ output = File.read(@out_file)
224
+ if output =~ /^RText service, listening on port (\d+)/
225
+ port = $1.to_i
226
+ @logger.info "connecting to #{port}" if @logger
227
+ begin
228
+ @socket = TCPSocket.new("127.0.0.1", port)
229
+ @socket.setsockopt(:SOCKET, :RCVBUF, 1000000)
230
+ rescue Errno::ECONNREFUSED
231
+ cleanup
232
+ @connection_listener.call(:timeout) if @connection_listener
233
+ @state = :off
234
+ @logger.warn "could not connect socket (connection timeout)" if @logger
235
+ return false
236
+ end
237
+ @state = :read_from_socket
238
+ @connection_listener.call(:connected) if @connection_listener
239
+ break
240
+ end
241
+ if Time.now > @connect_start_time + @connection_timeout
165
242
  cleanup
166
243
  @connection_listener.call(:timeout) if @connection_listener
167
- @work_state = :done
168
244
  @state = :off
169
245
  @logger.warn "could not connect socket (connection timeout)" if @logger
246
+ return false
170
247
  end
171
- @state = :connected
172
- @work_state = :read_from_socket
173
- @connection_listener.call(:connected) if @connection_listener
174
- end
175
- if Time.now > @connect_start_time + @connection_timeout
176
- cleanup
177
- @connection_listener.call(:timeout) if @connection_listener
178
- @work_state = :done
179
- @state = :off
180
- @logger.warn "could not connect socket (connection timeout)" if @logger
248
+ sleep(0.1)
181
249
  end
182
- true
183
- when :read_from_socket
250
+ end
251
+ if @state == :read_from_socket
184
252
  repeat = true
185
253
  socket_closed = false
186
254
  response_data = ""
@@ -208,13 +276,12 @@ def do_work
208
276
  end
209
277
  elsif !backend_running? || socket_closed
210
278
  cleanup
211
- @work_state = :done
279
+ @state = :off
212
280
  return false
213
281
  end
214
282
  end
215
- true
216
283
  end
217
-
284
+ true
218
285
  end
219
286
 
220
287
  def cleanup
@@ -224,7 +291,7 @@ def cleanup
224
291
  break unless backend_running?
225
292
  sleep(0.1)
226
293
  end
227
- File.unlink(@out_file) unless @keep_outfile
294
+ ensure_process_cleanup(@process_id, @keep_outfile ? @out_file : nil, 10)
228
295
  end
229
296
 
230
297
  end