rtext 0.8.0 → 0.9.3

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 (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