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
@@ -55,27 +55,27 @@ def filter_lines(lines)
55
55
  }
56
56
  end
57
57
 
58
- # when joining two lines, all whitespace after the last character of the first line is removed
59
- # (after , and [); however whitespace at the end of the last of several joined lines is preserved;
60
- # this way the context is correct even if the cursor is after the end of the last line
61
- # (i.e. with whitespace after the last non-whitespace character)
58
+ # when joining two lines, all whitespace is preserved in order to simplify the algorithm
59
+ # whitespace after a backslash is also preserved, only the backslash itself is removed
60
+ # note that whitespace left of the cursor is important for proper context calculation
62
61
  def join_lines(lines, pos)
63
62
  outlines = []
64
63
  while lines.size > 0
65
64
  outlines << lines.shift
66
65
  while lines.size > 0 &&
67
- (outlines.last =~ /,\s*$/ ||
68
- (outlines.last =~ /\[\s*$/ && outlines.last =~ /,/) ||
69
- (lines.first =~ /^\s*\]/ && outlines.last =~ /,/))
70
- outlines.last.rstrip!
66
+ (outlines.last =~ /[,\\]\s*$/ ||
67
+ # don't join after a child label
68
+ (outlines.last !~ /^\s*\w+:/ &&
69
+ (outlines.last =~ /\[\s*$/ ||
70
+ (lines.first =~ /^\s*\]/ && outlines.last =~ /\[/))))
71
71
  l = lines.shift
72
+ outlines.last.gsub!("\\","")
72
73
  if lines.size == 0
73
- # strip only left part, the prefix might have whitespace on the
74
+ # the prefix might have whitespace on the
74
75
  # right hand side which is relevant for the position
75
- non_ws_prefix = l[0..pos-1].lstrip
76
- pos = outlines.last.size + non_ws_prefix.size
76
+ pos = outlines.last.size + pos
77
77
  end
78
- outlines.last.concat(l.lstrip)
78
+ outlines.last.concat(l)
79
79
  end
80
80
  end
81
81
  [outlines, pos]
@@ -247,7 +247,15 @@ class Instantiator
247
247
  element.setOrAddGeneric(feature.name, proxy)
248
248
  else
249
249
  begin
250
- element.setOrAddGeneric(feature.name, v.value)
250
+ v_value = v.value
251
+ feature_instance_class = feature.eType.instanceClass
252
+ if feature_instance_class == String && (v_value.is_a?(Float) || v_value.is_a?(Fixnum))
253
+ element.setOrAddGeneric(feature.name, v_value.to_s)
254
+ elsif feature_instance_class == Float && v_value.is_a?(Fixnum)
255
+ element.setOrAddGeneric(feature.name, v_value.to_f)
256
+ else
257
+ element.setOrAddGeneric(feature.name, v_value)
258
+ end
251
259
  rescue StandardError
252
260
  # backward compatibility for RGen versions not supporting BigDecimal
253
261
  if v.value.is_a?(BigDecimal)
@@ -302,9 +310,9 @@ class Instantiator
302
310
  elsif feature.eType.is_a?(RGen::ECore::EEnum)
303
311
  [:identifier, :string]
304
312
  else
305
- expected = { String => [:string, :identifier],
313
+ expected = { String => [:string, :identifier, :integer, :float],
306
314
  Integer => [:integer],
307
- Float => [:float],
315
+ Float => [:float, :integer],
308
316
  RGen::MetamodelBuilder::DataTypes::Boolean => [:boolean],
309
317
  Object => [:string, :identifier, :integer, :float, :boolean]
310
318
  }[feature.eType.instanceClass]
@@ -260,7 +260,7 @@ class Language
260
260
  end
261
261
 
262
262
  def concrete_types(clazz)
263
- ([clazz] + clazz.eAllSubTypes).select{|c| !c.abstract}
263
+ ([clazz] + clazz.eAllSubTypes).select{|c| c.concrete}
264
264
  end
265
265
 
266
266
  def containments_by_target_type(clazz, type)
@@ -299,7 +299,7 @@ class Language
299
299
  @command_by_class = {}
300
300
  @has_command = {}
301
301
  root_epackage.eAllClasses.each do |c|
302
- next if c.abstract
302
+ next unless c.concrete
303
303
  cmd = command_name_provider.call(c)
304
304
  @command_by_class[c.instanceClass] = cmd
305
305
  @has_command[cmd] = true
@@ -307,7 +307,7 @@ class Language
307
307
  @class_by_command[clazz] ||= {}
308
308
  containments(c).collect{|r|
309
309
  [r.eType] + r.eType.eAllSubTypes}.flatten.uniq.each do |t|
310
- next if t.abstract
310
+ next unless t.concrete
311
311
  cmw = command_name_provider.call(t)
312
312
  raise "ambiguous command name #{cmw}" if @class_by_command[clazz][cmw]
313
313
  @class_by_command[clazz][cmw] = t.instanceClass
@@ -315,7 +315,7 @@ class Language
315
315
  end
316
316
  @class_by_command[nil] = {}
317
317
  @root_classes.each do |c|
318
- next if c.abstract
318
+ next unless c.concrete
319
319
  cmw = command_name_provider.call(c)
320
320
  raise "ambiguous command name #{cmw}" if @class_by_command[nil][cmw]
321
321
  @class_by_command[nil][cmw] = c.instanceClass
@@ -323,7 +323,7 @@ class Language
323
323
  end
324
324
 
325
325
  def default_root_classes(root_package)
326
- root_epackage.eAllClasses.select{|c| !c.abstract &&
326
+ root_epackage.eAllClasses.select{|c| c.concrete &&
327
327
  !c.eAllReferences.any?{|r| r.eOpposite && r.eOpposite.containment}}
328
328
  end
329
329
 
@@ -179,7 +179,7 @@ class Serializer
179
179
  result << v.to_s
180
180
  end
181
181
  elsif feature.eType.instanceClass == Object
182
- if v.to_s =~ /^\d+(\.\d+)?$|^\w+$|^true$|^false$/
182
+ if v.to_s =~ /^-?\d+(\.\d+)?$|^\w+$|^true$|^false$/
183
183
  result << v.to_s
184
184
  else
185
185
  result << "\"#{v.to_s.gsub("\\","\\\\\\\\").gsub("\"","\\\"").gsub("\n","\\n").
data/lib/rtext/service.rb CHANGED
@@ -1,253 +1,264 @@
1
- require 'socket'
2
- require 'rtext/context_builder'
3
- require 'rtext/message_helper'
4
- require 'rtext/link_detector'
5
-
6
- # optimization: garbage collect while service is idle
7
-
8
- module RText
9
-
10
- class Service
11
- include RText::MessageHelper
12
-
13
- PortRangeStart = 9001
14
- PortRangeEnd = 9100
15
-
16
- FlushInterval = 1
17
-
18
- # Creates an RText backend service. Options:
19
- #
20
- # :timeout
21
- # idle time in seconds after which the service will terminate itelf
22
- #
23
- # :logger
24
- # a logger object on which the service will write its logging output
25
- #
26
- # :on_startup:
27
- # a Proc which is called right after the service has started up
28
- # can be used to output version information
29
- #
30
- def initialize(service_provider, options={})
31
- @service_provider = service_provider
32
- @timeout = options[:timeout] || 60
33
- @logger = options[:logger]
34
- @on_startup = options[:on_startup]
35
- end
36
-
37
- def run
38
- server = create_server
39
- puts "RText service, listening on port #{server.addr[1]}"
40
- @on_startup.call if @on_startup
41
- $stdout.flush
42
-
43
- last_access_time = Time.now
44
- last_flush_time = Time.now
45
- @stop_requested = false
46
- sockets = []
47
- request_data = {}
48
- while !@stop_requested
49
- begin
50
- sock = server.accept_nonblock
51
- sock.sync = true
52
- sockets << sock
53
- @logger.info "accepted connection" if @logger
54
- rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINTR, Errno::EWOULDBLOCK
55
- rescue Exception => e
56
- @logger.warn "unexpected exception during socket accept: #{e.class}"
57
- end
58
- sockets.dup.each do |sock|
59
- data = nil
60
- begin
61
- data = sock.read_nonblock(100000)
62
- rescue Errno::EWOULDBLOCK
63
- rescue IOError, EOFError, Errno::ECONNRESET, Errno::ECONNABORTED
64
- sock.close
65
- request_data[sock] = nil
66
- sockets.delete(sock)
67
- rescue Exception => e
68
- # catch Exception to make sure we don't crash due to unexpected exceptions
69
- @logger.warn "unexpected exception during socket read: #{e.class}"
70
- sock.close
71
- request_data[sock] = nil
72
- sockets.delete(sock)
73
- end
74
- if data
75
- last_access_time = Time.now
76
- request_data[sock] ||= ""
77
- request_data[sock].concat(data)
78
- while obj = extract_message(request_data[sock])
79
- message_received(sock, obj)
80
- end
81
- end
82
- end
83
- IO.select([server] + sockets, [], [], 1)
84
- if Time.now > last_access_time + @timeout
85
- @logger.info("RText service, stopping now (timeout)") if @logger
86
- break
87
- end
88
- if Time.now > last_flush_time + FlushInterval
89
- $stdout.flush
90
- last_flush_time = Time.now
91
- end
92
- end
93
- end
94
-
95
- def message_received(sock, obj)
96
- if check_request(obj)
97
- request_start = Time.now
98
- @logger.debug("request: "+obj.inspect) if @logger
99
- response = { "type" => "response", "invocation_id" => obj["invocation_id"] }
100
- case obj["command"]
101
- when "load_model"
102
- load_model(sock, obj, response)
103
- when "content_complete"
104
- content_complete(sock, obj, response)
105
- when "link_targets"
106
- link_targets(sock, obj, response)
107
- when "find_elements"
108
- find_elements(sock, obj, response)
109
- when "stop"
110
- @logger.info("RText service, stopping now (stop requested)") if @logger
111
- @stop_requested = true
112
- else
113
- @logger.warn("unknown command #{obj["command"]}") if @logger
114
- response["type"] = "unknown_command_error"
115
- response["command"] = obj["command"]
116
- end
117
- @logger.debug("response: "+truncate_response_for_debug_output(response).inspect) \
118
- if response && @logger
119
- send_response(sock, response)
120
- @logger.info("request complete (#{Time.now-request_start}s)")
121
- end
122
- end
123
-
124
- private
125
-
126
- def check_request(obj)
127
- if obj["type"] != "request"
128
- @logger.warn("received message is not a request") if @logger
129
- false
130
- elsif !obj["invocation_id"].is_a?(Integer)
131
- @logger.warn("invalid invocation id #{obj["invocation_id"]}") if @logger
132
- false
133
- else
134
- true
135
- end
136
- end
137
-
138
- def load_model(sock, request, response)
139
- problems = @service_provider.get_problems(
140
- :on_progress => lambda do |frag, work_done, work_overall|
141
- work_overall = 1 if work_overall < 1
142
- work_done = work_overall if work_done > work_overall
143
- work_done = 0 if work_done < 0
144
- send_response(sock, {
145
- "type" => "progress",
146
- "invocation_id" => request["invocation_id"],
147
- "percentage" => work_done*100/work_overall
148
- })
149
- end)
150
- total = 0
151
- response["problems"] = problems.collect do |fp|
152
- { "file" => fp.file,
153
- "problems" => fp.problems.collect do |p|
154
- total += 1
155
- { "severity" => "error", "line" => p.line, "message" => p.message }
156
- end }
157
- end
158
- response["total_problems"] = total
159
- end
160
-
161
- InsertString = "insert"
162
- DisplayString = "display"
163
-
164
- def content_complete(sock, request, response)
165
- # column numbers start at 1
166
- linepos = request["column"]-1
167
- lines = request["context"]
168
- lang = @service_provider.language
169
- response["options"] = []
170
- return unless lang
171
- context = ContextBuilder.build_context(lang, lines, linepos)
172
- @logger.debug("context element: #{lang.identifier_provider.call(context.element, nil, nil, nil)}") \
173
- if context && context.element && @logger
174
- options = @service_provider.get_completion_options(context)
175
- insert_str = "insert"
176
- display_str = "display"
177
- response["options"] = options.collect do |o|
178
- { insert_str => o.text, display_str => "#{o.text} #{o.extra}" }
179
- end
180
- end
181
-
182
- def link_targets(sock, request, response)
183
- # column numbers start at 1
184
- linepos = request["column"]
185
- lines = request["context"]
186
- lang = @service_provider.language
187
- response["targets"] = []
188
- return unless lang
189
- link_descriptor = RText::LinkDetector.new(lang).detect(lines, linepos)
190
- if link_descriptor
191
- response["begin_column"] = link_descriptor.scol
192
- response["end_column"] = link_descriptor.ecol
193
- targets = []
194
- @service_provider.get_link_targets(link_descriptor).each do |t|
195
- targets << { "file" => t.file, "line" => t.line, "display" => t.display_name }
196
- end
197
- response["targets"] = targets
198
- end
199
- end
200
-
201
- def find_elements(sock, request, response)
202
- pattern = request["search_pattern"]
203
- total = 0
204
- response["elements"] = @service_provider.get_open_element_choices(pattern).collect do |c|
205
- total += 1
206
- { "display" => c.display_name, "file" => c.file, "line" => c.line }
207
- end
208
- response["total_elements"] = total
209
- end
210
-
211
- def send_response(sock, response)
212
- if response
213
- begin
214
- sock.write(serialize_message(response))
215
- sock.flush
216
- # if there is an exception, the next read should shutdown the connection properly
217
- rescue IOError, EOFError, Errno::ECONNRESET, Errno::ECONNABORTED
218
- rescue Exception => e
219
- # catch Exception to make sure we don't crash due to unexpected exceptions
220
- @logger.warn "unexpected exception during socket write: #{e.class}"
221
- end
222
- end
223
- end
224
-
225
- def truncate_response_for_debug_output(response_hash)
226
- result = {}
227
- response_hash.each_pair do |k,v|
228
- if v.is_a?(Array) && v.size > 100
229
- result[k] = v[0..99] + ["<truncated>"]
230
- else
231
- result[k] = v
232
- end
233
- end
234
- result
235
- end
236
-
237
- def create_server
238
- port = PortRangeStart
239
- serv = nil
240
- begin
241
- serv = TCPServer.new("127.0.0.1", port)
242
- rescue Errno::EADDRINUSE, Errno::EAFNOSUPPORT, Errno::EACCES
243
- port += 1
244
- retry if port <= PortRangeEnd
245
- raise
246
- end
247
- serv
248
- end
249
-
250
- end
251
-
252
- end
253
-
1
+ require 'socket'
2
+ require 'rtext/context_builder'
3
+ require 'rtext/message_helper'
4
+ require 'rtext/link_detector'
5
+
6
+ # optimization: garbage collect while service is idle
7
+
8
+ module RText
9
+
10
+ class Service
11
+ include RText::MessageHelper
12
+
13
+ PortRangeStart = 9001
14
+ PortRangeEnd = 9100
15
+
16
+ FlushInterval = 1
17
+ ProtocolVersion = 1
18
+
19
+ # Creates an RText backend service. Options:
20
+ #
21
+ # :timeout
22
+ # idle time in seconds after which the service will terminate itself
23
+ #
24
+ # :logger
25
+ # a logger object on which the service will write its logging output
26
+ #
27
+ # :on_startup:
28
+ # a Proc which is called right after the service has started up
29
+ # can be used to output version information
30
+ #
31
+ def initialize(service_provider, options={})
32
+ @service_provider = service_provider
33
+ @timeout = options[:timeout] || 60
34
+ @logger = options[:logger]
35
+ @on_startup = options[:on_startup]
36
+ end
37
+
38
+ def run
39
+ server = create_server
40
+ puts "RText service, listening on port #{server.addr[1]}"
41
+ @on_startup.call if @on_startup
42
+ $stdout.flush
43
+
44
+ last_access_time = Time.now
45
+ last_flush_time = Time.now
46
+ @stop_requested = false
47
+ sockets = []
48
+ request_data = {}
49
+ while !@stop_requested
50
+ begin
51
+ sock = server.accept_nonblock
52
+ sock.sync = true
53
+ sockets << sock
54
+ @logger.info "accepted connection" if @logger
55
+ rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINTR, Errno::EWOULDBLOCK
56
+ rescue Exception => e
57
+ @logger.warn "unexpected exception during socket accept: #{e.class}"
58
+ end
59
+ sockets.dup.each do |sock|
60
+ data = nil
61
+ begin
62
+ data = sock.read_nonblock(100000)
63
+ rescue Errno::EWOULDBLOCK
64
+ rescue IOError, EOFError, Errno::ECONNRESET, Errno::ECONNABORTED
65
+ sock.close
66
+ request_data[sock] = nil
67
+ sockets.delete(sock)
68
+ rescue Exception => e
69
+ # catch Exception to make sure we don't crash due to unexpected exceptions
70
+ @logger.warn "unexpected exception during socket read: #{e.class}"
71
+ sock.close
72
+ request_data[sock] = nil
73
+ sockets.delete(sock)
74
+ end
75
+ if data
76
+ last_access_time = Time.now
77
+ request_data[sock] ||= ""
78
+ request_data[sock].concat(data)
79
+ while obj = extract_message(request_data[sock])
80
+ message_received(sock, obj)
81
+ end
82
+ end
83
+ end
84
+ IO.select([server] + sockets, [], [], 1)
85
+ if Time.now > last_access_time + @timeout
86
+ @logger.info("RText service, stopping now (timeout)") if @logger
87
+ break
88
+ end
89
+ if Time.now > last_flush_time + FlushInterval
90
+ $stdout.flush
91
+ last_flush_time = Time.now
92
+ end
93
+ end
94
+ end
95
+
96
+ def message_received(sock, obj)
97
+ if check_request(obj)
98
+ request_start = Time.now
99
+ @logger.debug("request: "+obj.inspect) if @logger
100
+ response = { "type" => "response", "invocation_id" => obj["invocation_id"] }
101
+ case obj["command"]
102
+ when "version"
103
+ version(sock, obj, response)
104
+ when "load_model"
105
+ load_model(sock, obj, response)
106
+ when "content_complete"
107
+ content_complete(sock, obj, response)
108
+ when "link_targets"
109
+ link_targets(sock, obj, response)
110
+ when "find_elements"
111
+ find_elements(sock, obj, response)
112
+ when "stop"
113
+ @logger.info("RText service, stopping now (stop requested)") if @logger
114
+ @stop_requested = true
115
+ else
116
+ @logger.warn("unknown command #{obj["command"]}") if @logger
117
+ response["type"] = "unknown_command_error"
118
+ response["command"] = obj["command"]
119
+ end
120
+ @logger.debug("response: "+truncate_response_for_debug_output(response).inspect) \
121
+ if response && @logger
122
+ send_response(sock, response)
123
+ @logger.info("request complete (#{Time.now-request_start}s)")
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ def check_request(obj)
130
+ if obj["type"] != "request"
131
+ @logger.warn("received message is not a request") if @logger
132
+ false
133
+ elsif !obj["invocation_id"].is_a?(Integer)
134
+ @logger.warn("invalid invocation id #{obj["invocation_id"]}") if @logger
135
+ false
136
+ else
137
+ true
138
+ end
139
+ end
140
+
141
+ def version(sock, request, response)
142
+ response["version"] = ProtocolVersion
143
+ end
144
+
145
+ def load_model(sock, request, response)
146
+ problems = @service_provider.get_problems(
147
+ :on_progress => lambda do |frag, work_done, work_overall|
148
+ work_overall = 1 if work_overall < 1
149
+ work_done = work_overall if work_done > work_overall
150
+ work_done = 0 if work_done < 0
151
+ send_response(sock, {
152
+ "type" => "progress",
153
+ "invocation_id" => request["invocation_id"],
154
+ "percentage" => work_done*100/work_overall
155
+ })
156
+ end)
157
+ total = 0
158
+ response["problems"] = problems.collect do |fp|
159
+ { "file" => fp.file,
160
+ "problems" => fp.problems.collect do |p|
161
+ total += 1
162
+ { "severity" => "error", "line" => p.line, "message" => p.message }
163
+ end }
164
+ end
165
+ response["total_problems"] = total
166
+ end
167
+
168
+ InsertString = "insert"
169
+ DisplayString = "display"
170
+ DescriptionString = "desc"
171
+
172
+ def content_complete(sock, request, response)
173
+ # column numbers start at 1
174
+ linepos = request["column"]-1
175
+ lines = request["context"]
176
+ version = request["version"].to_i
177
+ lang = @service_provider.language
178
+ response["options"] = []
179
+ return unless lang
180
+ context = ContextBuilder.build_context(lang, lines, linepos)
181
+ @logger.debug("context element: #{lang.identifier_provider.call(context.element, nil, nil, nil)}") \
182
+ if context && context.element && @logger
183
+ if @service_provider.method(:get_completion_options).arity == 1
184
+ options = @service_provider.get_completion_options(context)
185
+ else
186
+ options = @service_provider.get_completion_options(context, version)
187
+ end
188
+ response["options"] = options.collect do |o|
189
+ { InsertString => o.insert, DisplayString => o.display, DescriptionString => o.description }
190
+ end
191
+ end
192
+
193
+ def link_targets(sock, request, response)
194
+ # column numbers start at 1
195
+ linepos = request["column"]
196
+ lines = request["context"]
197
+ lang = @service_provider.language
198
+ response["targets"] = []
199
+ return unless lang
200
+ link_descriptor = RText::LinkDetector.new(lang).detect(lines, linepos)
201
+ if link_descriptor
202
+ response["begin_column"] = link_descriptor.scol
203
+ response["end_column"] = link_descriptor.ecol
204
+ targets = []
205
+ @service_provider.get_link_targets(link_descriptor).each do |t|
206
+ targets << { "file" => t.file, "line" => t.line, "display" => t.display_name }
207
+ end
208
+ response["targets"] = targets
209
+ end
210
+ end
211
+
212
+ def find_elements(sock, request, response)
213
+ pattern = request["search_pattern"]
214
+ total = 0
215
+ response["elements"] = @service_provider.get_open_element_choices(pattern).collect do |c|
216
+ total += 1
217
+ { "display" => c.display_name, "file" => c.file, "line" => c.line }
218
+ end
219
+ response["total_elements"] = total
220
+ end
221
+
222
+ def send_response(sock, response)
223
+ if response
224
+ begin
225
+ sock.write(serialize_message(response))
226
+ sock.flush
227
+ # if there is an exception, the next read should shutdown the connection properly
228
+ rescue IOError, EOFError, Errno::ECONNRESET, Errno::ECONNABORTED
229
+ rescue Exception => e
230
+ # catch Exception to make sure we don't crash due to unexpected exceptions
231
+ @logger.warn "unexpected exception during socket write: #{e.class}"
232
+ end
233
+ end
234
+ end
235
+
236
+ def truncate_response_for_debug_output(response_hash)
237
+ result = {}
238
+ response_hash.each_pair do |k,v|
239
+ if v.is_a?(Array) && v.size > 100
240
+ result[k] = v[0..99] + ["<truncated>"]
241
+ else
242
+ result[k] = v
243
+ end
244
+ end
245
+ result
246
+ end
247
+
248
+ def create_server
249
+ port = PortRangeStart
250
+ serv = nil
251
+ begin
252
+ serv = TCPServer.new("127.0.0.1", port)
253
+ rescue Errno::EADDRINUSE, Errno::EAFNOSUPPORT, Errno::EACCES
254
+ port += 1
255
+ retry if port <= PortRangeEnd
256
+ raise
257
+ end
258
+ serv
259
+ end
260
+
261
+ end
262
+
263
+ end
264
+