puppet-editor-services 2.0.4

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 (109) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +510 -0
  3. data/CODEOWNERS +2 -0
  4. data/CODE_OF_CONDUCT.md +46 -0
  5. data/CONTRIBUTING.md +54 -0
  6. data/Gemfile +53 -0
  7. data/LICENSE +201 -0
  8. data/README.md +308 -0
  9. data/Rakefile +185 -0
  10. data/bin/puppet-debugserver +8 -0
  11. data/bin/puppet-languageserver +7 -0
  12. data/bin/puppet-languageserver-sidecar +7 -0
  13. data/lib/dsp/dsp.rb +7 -0
  14. data/lib/dsp/dsp_base.rb +62 -0
  15. data/lib/dsp/dsp_protocol.rb +4619 -0
  16. data/lib/lsp/lsp.rb +10 -0
  17. data/lib/lsp/lsp_base.rb +63 -0
  18. data/lib/lsp/lsp_custom.rb +170 -0
  19. data/lib/lsp/lsp_enums.rb +143 -0
  20. data/lib/lsp/lsp_protocol.rb +2785 -0
  21. data/lib/lsp/lsp_protocol_callhierarchy.proposed.rb +239 -0
  22. data/lib/lsp/lsp_protocol_colorprovider.rb +100 -0
  23. data/lib/lsp/lsp_protocol_configuration.rb +82 -0
  24. data/lib/lsp/lsp_protocol_declaration.rb +73 -0
  25. data/lib/lsp/lsp_protocol_foldingrange.rb +129 -0
  26. data/lib/lsp/lsp_protocol_implementation.rb +75 -0
  27. data/lib/lsp/lsp_protocol_progress.rb +200 -0
  28. data/lib/lsp/lsp_protocol_selectionrange.rb +79 -0
  29. data/lib/lsp/lsp_protocol_sematictokens.proposed.rb +340 -0
  30. data/lib/lsp/lsp_protocol_typedefinition.rb +75 -0
  31. data/lib/lsp/lsp_protocol_workspacefolders.rb +174 -0
  32. data/lib/lsp/lsp_types.rb +1534 -0
  33. data/lib/puppet-debugserver/debug_session/break_points.rb +137 -0
  34. data/lib/puppet-debugserver/debug_session/flow_control.rb +161 -0
  35. data/lib/puppet-debugserver/debug_session/hook_handlers.rb +295 -0
  36. data/lib/puppet-debugserver/debug_session/puppet_session_run_mode.rb +66 -0
  37. data/lib/puppet-debugserver/debug_session/puppet_session_state.rb +122 -0
  38. data/lib/puppet-debugserver/hooks.rb +132 -0
  39. data/lib/puppet-debugserver/message_handler.rb +277 -0
  40. data/lib/puppet-debugserver/puppet_debug_session.rb +541 -0
  41. data/lib/puppet-debugserver/puppet_monkey_patches.rb +118 -0
  42. data/lib/puppet-languageserver/client_session_state.rb +119 -0
  43. data/lib/puppet-languageserver/crash_dump.rb +50 -0
  44. data/lib/puppet-languageserver/epp/validation_provider.rb +34 -0
  45. data/lib/puppet-languageserver/facter_helper.rb +25 -0
  46. data/lib/puppet-languageserver/global_queues/sidecar_queue.rb +205 -0
  47. data/lib/puppet-languageserver/global_queues/single_instance_queue.rb +126 -0
  48. data/lib/puppet-languageserver/global_queues/validation_queue.rb +102 -0
  49. data/lib/puppet-languageserver/global_queues.rb +16 -0
  50. data/lib/puppet-languageserver/manifest/completion_provider.rb +331 -0
  51. data/lib/puppet-languageserver/manifest/definition_provider.rb +99 -0
  52. data/lib/puppet-languageserver/manifest/document_symbol_provider.rb +228 -0
  53. data/lib/puppet-languageserver/manifest/folding_provider.rb +226 -0
  54. data/lib/puppet-languageserver/manifest/format_on_type_provider.rb +143 -0
  55. data/lib/puppet-languageserver/manifest/hover_provider.rb +221 -0
  56. data/lib/puppet-languageserver/manifest/signature_provider.rb +169 -0
  57. data/lib/puppet-languageserver/manifest/validation_provider.rb +127 -0
  58. data/lib/puppet-languageserver/message_handler.rb +462 -0
  59. data/lib/puppet-languageserver/providers.rb +18 -0
  60. data/lib/puppet-languageserver/puppet_helper.rb +108 -0
  61. data/lib/puppet-languageserver/puppet_lexer_helper.rb +55 -0
  62. data/lib/puppet-languageserver/puppet_monkey_patches.rb +39 -0
  63. data/lib/puppet-languageserver/puppet_parser_helper.rb +212 -0
  64. data/lib/puppet-languageserver/puppetfile/validation_provider.rb +185 -0
  65. data/lib/puppet-languageserver/server_capabilities.rb +48 -0
  66. data/lib/puppet-languageserver/session_state/document_store.rb +272 -0
  67. data/lib/puppet-languageserver/session_state/language_client.rb +239 -0
  68. data/lib/puppet-languageserver/session_state/object_cache.rb +162 -0
  69. data/lib/puppet-languageserver/sidecar_protocol.rb +532 -0
  70. data/lib/puppet-languageserver/uri_helper.rb +46 -0
  71. data/lib/puppet-languageserver-sidecar/cache/base.rb +36 -0
  72. data/lib/puppet-languageserver-sidecar/cache/filesystem.rb +111 -0
  73. data/lib/puppet-languageserver-sidecar/cache/null.rb +27 -0
  74. data/lib/puppet-languageserver-sidecar/facter_helper.rb +41 -0
  75. data/lib/puppet-languageserver-sidecar/puppet_environment_monkey_patches.rb +52 -0
  76. data/lib/puppet-languageserver-sidecar/puppet_helper.rb +281 -0
  77. data/lib/puppet-languageserver-sidecar/puppet_modulepath_monkey_patches.rb +146 -0
  78. data/lib/puppet-languageserver-sidecar/puppet_monkey_patches.rb +9 -0
  79. data/lib/puppet-languageserver-sidecar/puppet_parser_helper.rb +77 -0
  80. data/lib/puppet-languageserver-sidecar/puppet_strings_helper.rb +399 -0
  81. data/lib/puppet-languageserver-sidecar/puppet_strings_monkey_patches.rb +16 -0
  82. data/lib/puppet-languageserver-sidecar/sidecar_protocol_extensions.rb +16 -0
  83. data/lib/puppet-languageserver-sidecar/workspace.rb +89 -0
  84. data/lib/puppet_debugserver.rb +164 -0
  85. data/lib/puppet_editor_services/connection/base.rb +62 -0
  86. data/lib/puppet_editor_services/connection/stdio.rb +25 -0
  87. data/lib/puppet_editor_services/connection/tcp.rb +34 -0
  88. data/lib/puppet_editor_services/handler/base.rb +16 -0
  89. data/lib/puppet_editor_services/handler/debug_adapter.rb +63 -0
  90. data/lib/puppet_editor_services/handler/json_rpc.rb +133 -0
  91. data/lib/puppet_editor_services/logging.rb +45 -0
  92. data/lib/puppet_editor_services/protocol/base.rb +27 -0
  93. data/lib/puppet_editor_services/protocol/debug_adapter.rb +135 -0
  94. data/lib/puppet_editor_services/protocol/debug_adapter_messages.rb +171 -0
  95. data/lib/puppet_editor_services/protocol/json_rpc.rb +241 -0
  96. data/lib/puppet_editor_services/protocol/json_rpc_messages.rb +200 -0
  97. data/lib/puppet_editor_services/server/base.rb +42 -0
  98. data/lib/puppet_editor_services/server/stdio.rb +85 -0
  99. data/lib/puppet_editor_services/server/tcp.rb +349 -0
  100. data/lib/puppet_editor_services/server.rb +15 -0
  101. data/lib/puppet_editor_services/version.rb +36 -0
  102. data/lib/puppet_editor_services.rb +8 -0
  103. data/lib/puppet_languageserver.rb +263 -0
  104. data/lib/puppet_languageserver_sidecar.rb +361 -0
  105. data/puppet-debugserver +11 -0
  106. data/puppet-editor-services.gemspec +29 -0
  107. data/puppet-languageserver +15 -0
  108. data/puppet-languageserver-sidecar +14 -0
  109. metadata +240 -0
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puppet_editor_services/protocol/json_rpc'
4
+
5
+ module PuppetEditorServices
6
+ module Protocol
7
+ module JsonRPCMessages
8
+ # Protocol message primitives
9
+ class Message
10
+ attr_accessor :jsonrpc
11
+
12
+ def initialize(initial_hash = nil)
13
+ @jsonrpc = ::PuppetEditorServices::Protocol::JsonRPC::JSONRPC_VERSION
14
+ from_h!(initial_hash)
15
+ end
16
+
17
+ def from_h!(value)
18
+ return self if value.nil? || value.empty?
19
+
20
+ # jsonrpc is a little special. Don't override it with nil. This
21
+ # allows `.new.from_h!(..)` to use the default without knowing exactly
22
+ # what version is used.
23
+ self.jsonrpc = value['jsonrpc'] unless value['jsonrpc'].nil?
24
+ self
25
+ end
26
+
27
+ def to_json(*options)
28
+ to_h.to_json(options)
29
+ end
30
+
31
+ def to_h
32
+ {
33
+ 'jsonrpc' => jsonrpc
34
+ }
35
+ end
36
+ end
37
+
38
+ # interface RequestMessage extends Message {
39
+ # /**
40
+ # * The request id.
41
+ # */
42
+ # id: number | string;
43
+ # /**
44
+ # * The method to be invoked.
45
+ # */
46
+ # method: string;
47
+ # /**
48
+ # * The method's params.
49
+ # */
50
+ # params?: Array<any> | object;
51
+ # }
52
+ class RequestMessage < Message
53
+ attr_accessor :id, :rpc_method, :params
54
+
55
+ def initialize(initial_hash = nil)
56
+ super
57
+ end
58
+
59
+ def from_h!(value)
60
+ value = {} if value.nil?
61
+ super
62
+ self.id = value['id']
63
+ self.rpc_method = value['method']
64
+ self.params = value['params']
65
+ self
66
+ end
67
+
68
+ def to_h
69
+ super.merge(
70
+ 'id' => id,
71
+ 'method' => rpc_method,
72
+ 'params' => params
73
+ )
74
+ end
75
+ end
76
+
77
+ # interface NotificationMessage extends Message {
78
+ # /**
79
+ # * The method to be invoked.
80
+ # */
81
+ # method: string;
82
+
83
+ # /**
84
+ # * The notification's params.
85
+ # */
86
+ # params?: Array<any> | object;
87
+ # }
88
+ class NotificationMessage < Message
89
+ attr_accessor :rpc_method, :params
90
+
91
+ def initialize(initial_hash = nil)
92
+ super
93
+ end
94
+
95
+ def from_h!(value)
96
+ value = {} if value.nil?
97
+ super
98
+ self.rpc_method = value['method']
99
+ self.params = value['params']
100
+ self
101
+ end
102
+
103
+ def to_h
104
+ hash = { 'method' => rpc_method }
105
+ hash['params'] = params unless params.nil?
106
+ super.merge(hash)
107
+ end
108
+ end
109
+
110
+ # interface ResponseMessage extends Message {
111
+ # /**
112
+ # * The request id.
113
+ # */
114
+ # id: number | string | null;
115
+
116
+ # /**
117
+ # * The result of a request. This member is REQUIRED on success.
118
+ # * This member MUST NOT exist if there was an error invoking the method.
119
+ # */
120
+ # result?: string | number | boolean | object | null;
121
+
122
+ # /**
123
+ # * The error object in case a request fails.
124
+ # */
125
+ # error?: ResponseError<any>;
126
+ # }
127
+ class ResponseMessage < Message
128
+ attr_accessor :id, :result, :error
129
+ # is_successful is special. It changes based on deserialising from hash or
130
+ # can be manually set. This affects serialisation
131
+ attr_accessor :is_successful
132
+
133
+ def initialize(initial_hash = nil)
134
+ super
135
+ end
136
+
137
+ def from_h!(value)
138
+ value = {} if value.nil?
139
+ super
140
+ self.id = value['id']
141
+ self.result = value['result']
142
+ self.error = value['error']
143
+ self.is_successful = value.key?('result')
144
+ self
145
+ end
146
+
147
+ def to_h
148
+ hash = { 'id' => id }
149
+ # Ref - https://www.jsonrpc.org/specification#response_object
150
+ if is_successful
151
+ # Succesful responses must ALWAYS have the result key, even if it's null.
152
+ hash['result'] = result
153
+ else
154
+ # Error responses must ALWAYS have the error key
155
+ # TODO: The RPC spec says error MUST be an Error object, not nil.
156
+ hash['error'] = error
157
+ end
158
+ super.merge(hash)
159
+ end
160
+ end
161
+
162
+ # Static message generators
163
+ def self.reply_result(request, result)
164
+ ResponseMessage.new.from_h!('id' => request.id, 'result' => result)
165
+ end
166
+
167
+ def self.reply_error(request, code, message)
168
+ reply_error_by_id(request.id, code, message)
169
+ end
170
+
171
+ def self.reply_error_by_id(id, code, message)
172
+ # Note - Strictly speaking the error should be typed object, however as this hidden behind
173
+ # this method it's easier to just pass in a known hash construct
174
+ ResponseMessage.new.from_h!(
175
+ 'id' => id,
176
+ 'error' => {
177
+ 'code' => code,
178
+ 'message' => message
179
+ }
180
+ )
181
+ end
182
+
183
+ def self.reply_method_not_found(request, message = nil)
184
+ reply_error(
185
+ request,
186
+ ::PuppetEditorServices::Protocol::JsonRPC::CODE_METHOD_NOT_FOUND,
187
+ message || ::PuppetEditorServices::Protocol::JsonRPC::MSG_METHOD_NOT_FOUND
188
+ )
189
+ end
190
+
191
+ def self.new_notification(method_name, params)
192
+ NotificationMessage.new.from_h!('method' => method_name, 'params' => params)
193
+ end
194
+
195
+ def self.new_request(id, method_name, params)
196
+ RequestMessage.new.from_h!('id' => id, 'method' => method_name, 'params' => params)
197
+ end
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puppet_editor_services/server'
4
+ require 'puppet_editor_services/connection/base'
5
+ require 'puppet_editor_services/protocol/base'
6
+
7
+ module PuppetEditorServices
8
+ module Server
9
+ class Base
10
+ attr_reader :server_options, :connection_options, :protocol_options, :handler_options
11
+
12
+ def name
13
+ 'SRV'
14
+ end
15
+
16
+ def initialize(server_options, connection_options, protocol_options, handler_options)
17
+ @server_options = server_options.nil? ? {} : server_options.dup
18
+ @connection_options = connection_options.nil? ? {} : connection_options.dup
19
+ @protocol_options = protocol_options.nil? ? {} : protocol_options.dup
20
+ @handler_options = handler_options.nil? ? {} : handler_options.dup
21
+
22
+ @connection_options[:class] = PuppetEditorServices::Connection::Base if @connection_options[:class].nil?
23
+ @protocol_options[:class] = PuppetEditorServices::Protocol::Base if @protocol_options[:class].nil?
24
+ @handler_options[:class] = PuppetEditorServices::Handler::Base if @handler_options[:class].nil?
25
+
26
+ @server_options[:servicename] = 'LANGUAGE SERVER' if @server_options[:servicename].nil?
27
+
28
+ # Assumes there's only ONE active simpler server running at a time.
29
+ PuppetEditorServices::Server.current_server = self
30
+ end
31
+
32
+ # Returns a client connection for a given connection_id
33
+ def connection(connection_id); end
34
+
35
+ def start; end
36
+
37
+ def log(message)
38
+ PuppetEditorServices.log_message(:debug, "#{name}: #{message}")
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puppet_editor_services/logging'
4
+ require 'puppet_editor_services/server/base'
5
+ require 'puppet_editor_services/connection/stdio'
6
+ require 'puppet_editor_services/protocol/base'
7
+
8
+ module PuppetEditorServices
9
+ module Server
10
+ class Stdio < ::PuppetEditorServices::Server::Base
11
+ attr_accessor :exiting
12
+
13
+ def name
14
+ 'STDIOSRV'
15
+ end
16
+
17
+ def initialize(server_options, protocol_options, handler_options)
18
+ super(server_options, {}, protocol_options, handler_options)
19
+ @exiting = false
20
+ end
21
+
22
+ def start
23
+ # This is a little heavy handed but we need to suppress writes to STDOUT and STDERR
24
+ $VERBOSE = nil
25
+ # Some libraries use $stdout to write to the console. Suppress all of that too!
26
+ # Copy the existing $stdout variable and then reassign to NUL to suppress it
27
+ $editor_services_stdout = $stdout # rubocop:disable Style/GlobalVars We need this global var
28
+ $stdout = File.open(File::NULL, 'w')
29
+
30
+ $editor_services_stdout.sync = true # rubocop:disable Style/GlobalVars We need this global var
31
+ # Stop the stupid CRLF injection when on Windows
32
+ $editor_services_stdout.binmode unless $editor_services_stdout.binmode # rubocop:disable Style/GlobalVars We need this global var
33
+
34
+ @client_connection = PuppetEditorServices::Connection::Stdio.new(self)
35
+
36
+ log('Starting STDIO server...')
37
+ loop do
38
+ inbound_data = nil
39
+ read_from_pipe($stdin, 2) { |data| inbound_data = data }
40
+ break if @exiting
41
+
42
+ @client_connection.receive_data(inbound_data) unless inbound_data.nil?
43
+ break if @exiting
44
+ end
45
+ log('STDIO server stopped')
46
+ end
47
+
48
+ def stop
49
+ log('Stopping STDIO server...')
50
+ @exiting = true
51
+ end
52
+
53
+ def connection(connection_id)
54
+ @client_connection unless @client_connection.nil? || @client_connection.id != connection_id
55
+ end
56
+
57
+ def close_connection
58
+ stop
59
+ end
60
+
61
+ def pipe_is_readable?(stream, timeout = 0.5)
62
+ read_ready = IO.select([stream], [], [], timeout)
63
+ read_ready && stream == read_ready[0][0]
64
+ end
65
+
66
+ def read_from_pipe(pipe, timeout = 0.1, &_block)
67
+ if pipe_is_readable?(pipe, timeout)
68
+ l = nil
69
+ begin
70
+ l = pipe.readpartial(4096)
71
+ rescue EOFError
72
+ log('Reading from pipe has reached End of File. Exiting STDIO server')
73
+ stop
74
+ rescue # rubocop:disable Style/RescueStandardError
75
+ # Any errors here should be swallowed because the pipe could be in any state
76
+ end
77
+ # since readpartial may return a nil at EOF, skip returning that value
78
+ # client_connected = true unless l.nil?
79
+ yield l unless l.nil?
80
+ end
81
+ nil
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,349 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+ require 'openssl'
5
+
6
+ # frozen_string_literal: true
7
+
8
+ require 'puppet_editor_services/logging'
9
+ require 'puppet_editor_services/server/base'
10
+ require 'puppet_editor_services/connection/tcp'
11
+ require 'puppet_editor_services/protocol/base'
12
+
13
+ # Based on code from
14
+ # http://stackoverflow.com/questions/29858113/unable-to-make-socket-accept-non-blocking-ruby-2-2
15
+
16
+ module PuppetEditorServices
17
+ module Server
18
+ class Tcp < ::PuppetEditorServices::Server::Base
19
+ class << self
20
+ attr_reader :io_locker, :events, :e_locker, :services, :s_locker, :io_connection_dic, :c_locker
21
+ end
22
+
23
+ @io_locker = Mutex.new
24
+ @events = []
25
+ @e_locker = Mutex.new
26
+ @services = {}
27
+ @s_locker = Mutex.new
28
+ @io_connection_dic = {}
29
+ @c_locker = Mutex.new
30
+
31
+ def initialize(server_options, protocol_options, handler_options)
32
+ super(server_options, {}, protocol_options, handler_options)
33
+
34
+ add_service(server_options[:ipaddress], server_options[:port])
35
+ end
36
+
37
+ def name
38
+ 'TCPSRV'
39
+ end
40
+
41
+ ####
42
+ # this code will be called when a socket recieves data.
43
+ # @api private
44
+ def get_data(io, connection_data)
45
+ data = io.recv_nonblock(1_048_576) # with maximum number of bytes to read at a time...
46
+ raise 'Received a 0byte payload' if data.length.zero?
47
+
48
+ # We're already in a callback so no need to invoke as a callback
49
+ connection_data[:handler].receive_data(data)
50
+ rescue StandardError => e
51
+ log("Closing socket due to error - #{e}\n#{e.backtrace}")
52
+ remove_connection(io)
53
+ end
54
+
55
+ #########
56
+ # main loop and activation code
57
+ #
58
+ # This will create a thread pool and set them running.
59
+ # @api public
60
+ def start
61
+ # prepare threads
62
+ max_threads = server_options[:max_threads] || 2
63
+ exit_flag = false
64
+ threads = []
65
+ thread_cycle = proc do
66
+ begin
67
+ io_review
68
+ rescue # rubocop:disable Style/RescueStandardError
69
+ # Swallow all errors
70
+ false
71
+ end
72
+ true while fire_event
73
+ end
74
+ max_threads.times { Thread.new { thread_cycle.call until exit_flag } }
75
+
76
+ log('Services running. Press ^C to stop')
77
+
78
+ # sleep until trap raises exception (cycling might cause the main thread to loose signals that might be caught inside rescue clauses)
79
+ kill_timer = server_options[:connection_timeout]
80
+ kill_timer = -1 if kill_timer.nil? || kill_timer < 1
81
+ log("Will stop the server in #{server_options[:connection_timeout]} seconds if no connection is made.") if kill_timer > 0
82
+ log('Will stop the server when client disconnects') if !server_options[:stop_on_client_exit].nil? && server_options[:stop_on_client_exit]
83
+
84
+ # Output to STDOUT. This is required by clients so it knows the server is now running
85
+ self.class.s_locker.synchronize do
86
+ self.class.services.each_value do |service|
87
+ $stdout.write("#{server_options[:servicename]} RUNNING #{service[:hostname]}:#{service[:port]}\n")
88
+ end
89
+ end
90
+ $stdout.flush
91
+
92
+ loop do
93
+ begin
94
+ sleep(1)
95
+ # The kill_timer is used to stop the server if no clients have connected in X seconds
96
+ # a value of 0 or less will not timeout.
97
+ if kill_timer > 0
98
+ kill_timer -= 1
99
+ if kill_timer.zero?
100
+ connection_count = 0
101
+ self.class.c_locker.synchronize { connection_count = self.class.io_connection_dic.count }
102
+ if connection_count.zero?
103
+ log("No connection has been received in #{server_options[:connection_timeout]} seconds. Shutting down server.")
104
+ stop_services
105
+ end
106
+ end
107
+ end
108
+ rescue # rubocop:disable Style/RescueStandardError
109
+ # Swallow all errors
110
+ true
111
+ end
112
+ break if self.class.services.empty?
113
+ end
114
+
115
+ # start shutdown.
116
+ exit_flag = true
117
+ log('Started shutdown process. Press ^C to force quit.')
118
+ # shut down listening sockets
119
+ stop_services
120
+ # disconnect active connections
121
+ stop_connections
122
+ # cycle down threads
123
+ log('Waiting for workers to cycle down')
124
+ threads.each { |t| t.join if t.alive? }
125
+
126
+ # rundown any active events
127
+ thread_cycle.call
128
+ end
129
+
130
+ #######################
131
+ ## Events (Callbacks) / Multi-tasking Platform
132
+ # returns true if there are any unhandled events
133
+ # @api private
134
+ def events?
135
+ self.class.e_locker.synchronize { !self.class.events.empty? }
136
+ end
137
+
138
+ # pushes an event to the event's stack
139
+ # if a block is passed along, it will be used as a callback: the block will be called with the values returned by the handler's `call` method.
140
+ # @api private
141
+ def push_event(handler, *args, &block)
142
+ if block
143
+ self.class.e_locker.synchronize { self.class.events << [proc { |a| push_event block, handler.call(*a) }, args] }
144
+ else
145
+ self.class.e_locker.synchronize { self.class.events << [handler, args] }
146
+ end
147
+ end
148
+
149
+ # Runs the block asynchronously by pushing it as an event to the event's stack
150
+ #
151
+ # @api private
152
+ def run_async(*args, &block)
153
+ self.class.e_locker.synchronize { self.class.events << [block, args] } if block
154
+ !block.nil?
155
+ end
156
+
157
+ # creates an asynchronous call to a method, with an optional callback (shortcut)
158
+ # @api private
159
+ def callback(object, method, *args, &block)
160
+ push_event object.method(method), *args, &block
161
+ end
162
+
163
+ # event handling FIFO
164
+ # @api private
165
+ def fire_event
166
+ event = self.class.e_locker.synchronize { self.class.events.shift }
167
+ return false unless event
168
+
169
+ begin
170
+ event[0].call(*event[1])
171
+ rescue OpenSSL::SSL::SSLError
172
+ log('SSL Bump - SSL Certificate refused?')
173
+ # rubocop:disable Lint/RescueException
174
+ rescue Exception => e
175
+ raise if e.is_a?(SignalException) || e.is_a?(SystemExit)
176
+ end
177
+ # rubocop:enable Lint/RescueException
178
+
179
+ true
180
+ end
181
+
182
+ #####
183
+ # Reactor
184
+ #
185
+ # IO review code will review the connections and sockets
186
+ # it will accept new connections and react to socket input
187
+ # @api private
188
+ def io_review
189
+ self.class.io_locker.synchronize do
190
+ return false unless self.class.events.empty?
191
+
192
+ united = self.class.services.keys + self.class.io_connection_dic.keys
193
+ return false if united.empty?
194
+
195
+ io_r = IO.select(united, nil, united, 0.1)
196
+ if io_r
197
+ io_r[0].each do |io|
198
+ if self.class.services[io]
199
+ begin
200
+ callback(self, :add_connection, io.accept_nonblock, self.class.services[io])
201
+ rescue Errno::EWOULDBLOCK
202
+ # There's nothing to handle. Swallow the error
203
+ rescue StandardError => e
204
+ log(e.message)
205
+ end
206
+ elsif self.class.io_connection_dic[io]
207
+ callback(self, :get_data, io, self.class.io_connection_dic[io])
208
+ else
209
+ log('what?!')
210
+ remove_connection(io)
211
+ self.class.services.delete(io)
212
+ end
213
+ end
214
+ io_r[2].each do |io|
215
+ (remove_connection(io) || self.class.services.delete(io)).close
216
+ rescue # rubocop:disable Style/RescueStandardError
217
+ # Swallow all errors
218
+ true
219
+ end
220
+ end
221
+ end
222
+ callback self, :clear_connections
223
+ true
224
+ end
225
+
226
+ #######################
227
+ # IO - listening sockets (services)
228
+
229
+ # @api private
230
+ def add_service(hostname = 'localhost', port = nil, parameters = {})
231
+ hostname = 'localhost' if hostname.nil? || hostname.empty?
232
+ service = TCPServer.new(hostname, port)
233
+ parameters[:hostname] = hostname
234
+ parameters[:port] = service.local_address.ip_port
235
+ self.class.s_locker.synchronize { self.class.services[service] = parameters }
236
+ callback(self, :log, "Started listening on #{hostname}:#{parameters[:port]}.")
237
+ true
238
+ end
239
+
240
+ # @api public
241
+ def stop_services(from_trap = false)
242
+ log('Stopping services')
243
+ if from_trap
244
+ # synchronize is not allowed when called from a trap statement
245
+ stop_all_services
246
+ else
247
+ self.class.s_locker.synchronize do
248
+ stop_all_services
249
+ end
250
+ end
251
+ end
252
+
253
+ # @api private
254
+ def stop_all_services
255
+ self.class.services.each do |s, p|
256
+ begin
257
+ s.close
258
+ rescue # rubocop:disable Style/RescueStandardError
259
+ # Swallow all errors
260
+ true
261
+ end
262
+ log("Stopped listening on #{p[:hostname]}:#{p[:port]}")
263
+ end
264
+ self.class.services.clear
265
+ end
266
+
267
+ # @api public
268
+ def remove_connection_async(io)
269
+ callback(self, :remove_connection, io)
270
+ end
271
+
272
+ #####################
273
+ # IO - Active connections handling
274
+
275
+ # @api private
276
+ def stop_connections
277
+ self.class.c_locker.synchronize do
278
+ self.class.io_connection_dic.each_key do |io|
279
+ io.close
280
+ rescue # rubocop:disable Style/RescueStandardError
281
+ # Swallow all errors
282
+ true
283
+ end
284
+ self.class.io_connection_dic.clear
285
+ end
286
+ end
287
+
288
+ # @api private
289
+ def add_connection(io, service_object)
290
+ conn = ::PuppetEditorServices::Connection::Tcp.new(self, io)
291
+ if io
292
+ self.class.c_locker.synchronize do
293
+ self.class.io_connection_dic[io] = { handler: conn, service: service_object }
294
+ end
295
+ end
296
+ callback(conn, :post_init)
297
+ rescue Exception => e # rubocop:disable Lint/RescueException Need to swallow all errors here
298
+ callback(self, :log, "Error creating connection #{e.inspect}\n#{e.backtrace}")
299
+ end
300
+
301
+ # @api public
302
+ def connection(connection_id)
303
+ self.class.c_locker.synchronize do
304
+ self.class.io_connection_dic.each_value do |v|
305
+ return v[:handler] unless v[:handler].nil? || v[:handler].id != connection_id
306
+ end
307
+ end
308
+ nil
309
+ end
310
+
311
+ # @api private
312
+ def remove_connection(io)
313
+ # This needs to be synchronous
314
+ begin
315
+ self.class.io_connection_dic[io][:handler].unbind
316
+ rescue e
317
+ # Any errors when unbinding the handler should NOT stop the underlying socket
318
+ # from being closed
319
+ log("Error unbinding #{e.inspect}\n#{e.backtrace}")
320
+ end
321
+ connection_count = 0
322
+ self.class.c_locker.synchronize do
323
+ self.class.io_connection_dic.delete io
324
+ connection_count = self.class.io_connection_dic.count
325
+ begin
326
+ io.close
327
+ rescue # rubocop:disable Style/RescueStandardError
328
+ # Swallow all errors
329
+ true
330
+ end
331
+ end
332
+
333
+ return unless connection_count.zero? && !@server_options[:stop_on_client_exit].nil? && @server_options[:stop_on_client_exit]
334
+
335
+ callback(self, :log, 'All clients have disconnected. Shutting down server.')
336
+ callback(self, :stop_services)
337
+ end
338
+
339
+ # clears closed connections from the stack
340
+ # @api private
341
+ def clear_connections
342
+ # Using a SymbolProc here does not work
343
+ # rubocop:disable Style/SymbolProc
344
+ self.class.c_locker.synchronize { self.class.io_connection_dic.delete_if { |c| c.closed? } }
345
+ # rubocop:enable Style/SymbolProc
346
+ end
347
+ end
348
+ end
349
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puppet_editor_services/server/base'
4
+
5
+ module PuppetEditorServices
6
+ module Server
7
+ def self.current_server
8
+ @@current_server # This is fine
9
+ end
10
+
11
+ def self.current_server=(value)
12
+ @@current_server = value # rubocop:disable Style/ClassVars This is fine
13
+ end
14
+ end
15
+ end