union_station_hooks_core 1.0.0
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.
- checksums.yaml +15 -0
- data/LICENSE.md +19 -0
- data/README.md +117 -0
- data/lib/union_station_hooks_core.rb +360 -0
- data/lib/union_station_hooks_core/api.rb +182 -0
- data/lib/union_station_hooks_core/connection.rb +67 -0
- data/lib/union_station_hooks_core/context.rb +297 -0
- data/lib/union_station_hooks_core/lock.rb +62 -0
- data/lib/union_station_hooks_core/log.rb +66 -0
- data/lib/union_station_hooks_core/message_channel.rb +157 -0
- data/lib/union_station_hooks_core/request_reporter.rb +141 -0
- data/lib/union_station_hooks_core/request_reporter/basics.rb +132 -0
- data/lib/union_station_hooks_core/request_reporter/controllers.rb +187 -0
- data/lib/union_station_hooks_core/request_reporter/misc.rb +218 -0
- data/lib/union_station_hooks_core/request_reporter/view_rendering.rb +76 -0
- data/lib/union_station_hooks_core/spec_helper.rb +241 -0
- data/lib/union_station_hooks_core/time_point.rb +53 -0
- data/lib/union_station_hooks_core/transaction.rb +182 -0
- data/lib/union_station_hooks_core/utils.rb +161 -0
- data/lib/union_station_hooks_core/version.rb +32 -0
- data/lib/union_station_hooks_core/version_data.rb +44 -0
- metadata +64 -0
@@ -0,0 +1,182 @@
|
|
1
|
+
# Union Station - https://www.unionstationapp.com/
|
2
|
+
# Copyright (c) 2010-2015 Phusion Holding B.V.
|
3
|
+
#
|
4
|
+
# "Union Station" and "Passenger" are trademarks of Phusion Holding B.V.
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
# of this software and associated documentation files (the "Software"), to deal
|
8
|
+
# in the Software without restriction, including without limitation the rights
|
9
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
# copies of the Software, and to permit persons to whom the Software is
|
11
|
+
# furnished to do so, subject to the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
14
|
+
# all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
# THE SOFTWARE.
|
23
|
+
|
24
|
+
UnionStationHooks.require_lib 'utils'
|
25
|
+
UnionStationHooks.require_lib 'time_point'
|
26
|
+
UnionStationHooks.require_lib 'request_reporter'
|
27
|
+
|
28
|
+
module UnionStationHooks
|
29
|
+
class << self
|
30
|
+
# @note You do not have to call this! Passenger automatically calls
|
31
|
+
# this for you! Just obtain the RequestReporter object that has been made
|
32
|
+
# available for you.
|
33
|
+
#
|
34
|
+
# Indicates that a Rack request has begun. Given a Rack environment hash,
|
35
|
+
# this method returns {RequestReporter} object, which you can use for
|
36
|
+
# logging Union Station information about this request. This method should
|
37
|
+
# be called as early as possible during a request, before any processing
|
38
|
+
# has begun. Only after calling this method will it be possible to log
|
39
|
+
# request-specific information to Union Station.
|
40
|
+
#
|
41
|
+
# The {RequestReporter} object that this method creates is also made
|
42
|
+
# available through the `union_station_hooks` key in the Rack environment
|
43
|
+
# hash, as well as the `:union_station_hooks` key in the current thread's
|
44
|
+
# object:
|
45
|
+
#
|
46
|
+
# env['union_station_hooks']
|
47
|
+
# # => RequestReporter object or nil
|
48
|
+
#
|
49
|
+
# Thread.current[:union_station_hooks]
|
50
|
+
# # => RequestReporter object or nil
|
51
|
+
#
|
52
|
+
# If this method was already called on this Rack request, then this method
|
53
|
+
# does nothing and merely returns the previously created {RequestReporter}.
|
54
|
+
#
|
55
|
+
# See {RequestReporter} to learn what kind of information you can log to
|
56
|
+
# Union Station about Rack requests.
|
57
|
+
#
|
58
|
+
# @return [RequestReporter, nil] A {RequestReporter} object, or nil or
|
59
|
+
# because of certain error conditions. See the RequestReporter class
|
60
|
+
# description for when this may be nil.
|
61
|
+
def begin_rack_request(rack_env)
|
62
|
+
reporter = rack_env['union_station_hooks']
|
63
|
+
return reporter if reporter
|
64
|
+
|
65
|
+
# PASSENGER_TXN_ID may be nil here even if Union Station support is
|
66
|
+
# enabled. For example, if the user tried to access the application
|
67
|
+
# process directly through its private HTTP socket.
|
68
|
+
txn_id = rack_env['PASSENGER_TXN_ID']
|
69
|
+
return nil if !txn_id
|
70
|
+
|
71
|
+
reporter = RequestReporter.new(context, txn_id, app_group_name, key)
|
72
|
+
return if reporter.null?
|
73
|
+
|
74
|
+
rack_env['union_station_hooks'] = reporter
|
75
|
+
Thread.current[:union_station_hooks] = reporter
|
76
|
+
reporter.log_request_begin
|
77
|
+
reporter.log_gc_stats_on_request_begin
|
78
|
+
reporter
|
79
|
+
end
|
80
|
+
|
81
|
+
# @note You do not have to call this! Passenger automatically calls
|
82
|
+
# this for you!
|
83
|
+
#
|
84
|
+
# Indicates that a Rack request, on which {begin_rack_request} was called,
|
85
|
+
# has ended. You should call this method as late as possible during a
|
86
|
+
# request, after all processing have ended. Preferably after the Rack
|
87
|
+
# response body has closed.
|
88
|
+
#
|
89
|
+
# The {RequestReporter} object associated with this Rack request and with
|
90
|
+
# the current, will be closed (by calling {RequestReporter#close}), which
|
91
|
+
# finalizes the Union Station logs for this request.
|
92
|
+
#
|
93
|
+
# This method MUST be called in the same thread that called
|
94
|
+
# {begin_rack_request}.
|
95
|
+
#
|
96
|
+
# It is undefined what will happen if you call this method a Rack request
|
97
|
+
# on which {begin_rack_request} was not called, so don't do that.
|
98
|
+
#
|
99
|
+
# This method does nothing if it was already called on this Rack request.
|
100
|
+
def end_rack_request(rack_env,
|
101
|
+
uncaught_exception_raised_during_request = false)
|
102
|
+
reporter = rack_env.delete('union_station_hooks')
|
103
|
+
Thread.current[:union_station_hooks] = nil
|
104
|
+
if reporter
|
105
|
+
begin
|
106
|
+
reporter.log_gc_stats_on_request_end
|
107
|
+
reporter.log_request_end(uncaught_exception_raised_during_request)
|
108
|
+
ensure
|
109
|
+
reporter.close
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Returns an opaque object (a {TimePoint}) that represents a collection
|
115
|
+
# of metrics about the current time.
|
116
|
+
#
|
117
|
+
# Various API methods expect you to provide timing information. They
|
118
|
+
# accept standard Ruby `Time` objects, but it is generally better to
|
119
|
+
# pass `TimePoint` objects. Unlike the standard Ruby `Time` object,
|
120
|
+
# which only contains the wall clock time (the real time), `TimePoint`
|
121
|
+
# may contain additional timing information such as CPU time, time
|
122
|
+
# spent in userspace and kernel space, time spent context switching,
|
123
|
+
# etc. The exact information contained in the object is operating
|
124
|
+
# system specific, hence why the object is meant to be opaque.
|
125
|
+
#
|
126
|
+
# See {RequestReporter#log_controller_action_happened} for an example of
|
127
|
+
# an API method which expects timing information.
|
128
|
+
# `RequestReporter#log_controller_action_happened` expects you to
|
129
|
+
# provide timing information about a controller action. That timing
|
130
|
+
# information is supposed to be obtained by calling
|
131
|
+
# `UnionStationHooks.now`.
|
132
|
+
#
|
133
|
+
# In all API methods that expect a `TimePoint`, you can also pass a
|
134
|
+
# normal Ruby `Time` object instead. But if you do that, the logged
|
135
|
+
# timing information will be less detailed. Only do this if you cannot
|
136
|
+
# obtain a `TimePoint` object for some reason.
|
137
|
+
#
|
138
|
+
# @return [TimePoint]
|
139
|
+
def now
|
140
|
+
pt = Utils.process_times
|
141
|
+
TimePoint.new(Time.now, pt.utime, pt.stime)
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def create_context
|
147
|
+
require_lib('context')
|
148
|
+
@@context = Context.new(config[:ust_router_address],
|
149
|
+
config[:ust_router_username] || 'logging',
|
150
|
+
config[:ust_router_password],
|
151
|
+
config[:node_name])
|
152
|
+
end
|
153
|
+
|
154
|
+
def install_event_pre_hook
|
155
|
+
preprocessor = @@config[:event_preprocessor]
|
156
|
+
if preprocessor
|
157
|
+
define_singleton_method(:call_event_pre_hook, &preprocessor)
|
158
|
+
else
|
159
|
+
def call_event_pre_hook(_event)
|
160
|
+
# Do nothing
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def initialize_other_union_station_hooks_gems
|
166
|
+
@@initializers.each do |initializer|
|
167
|
+
initializer.initialize!
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def finalize_install
|
172
|
+
@@config.freeze
|
173
|
+
@@initializers.freeze
|
174
|
+
@@app_group_name = @@config[:app_group_name]
|
175
|
+
@@key = @@config[:union_station_key]
|
176
|
+
if @@config[:debug]
|
177
|
+
UnionStationHooks::Log.debugging = true
|
178
|
+
end
|
179
|
+
@@initialized = true
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# Union Station - https://www.unionstationapp.com/
|
2
|
+
# Copyright (c) 2010-2015 Phusion Holding B.V.
|
3
|
+
#
|
4
|
+
# "Union Station" and "Passenger" are trademarks of Phusion Holding B.V.
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
# of this software and associated documentation files (the "Software"), to deal
|
8
|
+
# in the Software without restriction, including without limitation the rights
|
9
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
# copies of the Software, and to permit persons to whom the Software is
|
11
|
+
# furnished to do so, subject to the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
14
|
+
# all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
# THE SOFTWARE.
|
23
|
+
|
24
|
+
require 'thread'
|
25
|
+
UnionStationHooks.require_lib 'message_channel'
|
26
|
+
|
27
|
+
module UnionStationHooks
|
28
|
+
# Represents a connection to the UstRouter process.
|
29
|
+
#
|
30
|
+
# @private
|
31
|
+
class Connection
|
32
|
+
attr_reader :mutex
|
33
|
+
attr_accessor :channel
|
34
|
+
|
35
|
+
def initialize(io)
|
36
|
+
@mutex = Mutex.new
|
37
|
+
@refcount = 1
|
38
|
+
@channel = MessageChannel.new(io) if io
|
39
|
+
end
|
40
|
+
|
41
|
+
def connected?
|
42
|
+
!!@channel
|
43
|
+
end
|
44
|
+
|
45
|
+
def disconnect
|
46
|
+
@channel.io.close if @channel
|
47
|
+
@channel = nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def ref
|
51
|
+
@refcount += 1
|
52
|
+
end
|
53
|
+
|
54
|
+
def unref
|
55
|
+
@refcount -= 1
|
56
|
+
if @refcount == 0
|
57
|
+
disconnect
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def synchronize
|
62
|
+
@mutex.synchronize do
|
63
|
+
yield
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,297 @@
|
|
1
|
+
# Union Station - https://www.unionstationapp.com/
|
2
|
+
# Copyright (c) 2010-2015 Phusion Holding B.V.
|
3
|
+
#
|
4
|
+
# "Union Station" and "Passenger" are trademarks of Phusion Holding B.V.
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
# of this software and associated documentation files (the "Software"), to deal
|
8
|
+
# in the Software without restriction, including without limitation the rights
|
9
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
# copies of the Software, and to permit persons to whom the Software is
|
11
|
+
# furnished to do so, subject to the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
14
|
+
# all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
# THE SOFTWARE.
|
23
|
+
|
24
|
+
require 'thread'
|
25
|
+
require 'socket'
|
26
|
+
UnionStationHooks.require_lib 'connection'
|
27
|
+
UnionStationHooks.require_lib 'transaction'
|
28
|
+
UnionStationHooks.require_lib 'log'
|
29
|
+
UnionStationHooks.require_lib 'lock'
|
30
|
+
UnionStationHooks.require_lib 'utils'
|
31
|
+
|
32
|
+
module UnionStationHooks
|
33
|
+
# A Context object is the "heart" of all `union_station_hooks_*` gems. It
|
34
|
+
# contains a connection to the UstRouter (through a Connection object)
|
35
|
+
# and allows you to create Transaction objects.
|
36
|
+
#
|
37
|
+
# Context is a singleton. During initialization
|
38
|
+
# (`UnionStationHooks.initialize!`), an instance is created and stored in
|
39
|
+
# `UnionStationHooks.context`. All the public API methods make use of this
|
40
|
+
# singleton context.
|
41
|
+
#
|
42
|
+
# See hacking/Architecture.md for an overview.
|
43
|
+
#
|
44
|
+
# @private
|
45
|
+
class Context
|
46
|
+
RETRY_SLEEP = 0.2
|
47
|
+
NETWORK_ERRORS = [
|
48
|
+
Errno::EPIPE, Errno::ECONNREFUSED, Errno::ECONNRESET,
|
49
|
+
Errno::EHOSTUNREACH, Errno::ENETDOWN, Errno::ENETUNREACH,
|
50
|
+
Errno::ETIMEDOUT
|
51
|
+
]
|
52
|
+
|
53
|
+
include Utils
|
54
|
+
|
55
|
+
attr_accessor :max_connect_tries
|
56
|
+
attr_accessor :reconnect_timeout
|
57
|
+
|
58
|
+
def initialize(ust_router_address, username, password, node_name)
|
59
|
+
@server_address = ust_router_address
|
60
|
+
@username = username
|
61
|
+
@password = password
|
62
|
+
if node_name && !node_name.empty?
|
63
|
+
@node_name = node_name
|
64
|
+
else
|
65
|
+
@node_name = `hostname`.strip
|
66
|
+
end
|
67
|
+
@random_dev = File.open('/dev/urandom')
|
68
|
+
|
69
|
+
# This mutex protects the following instance variables, but
|
70
|
+
# not the contents of @connection.
|
71
|
+
@mutex = Mutex.new
|
72
|
+
|
73
|
+
@connection = Connection.new(nil)
|
74
|
+
if @server_address && local_socket_address?(@server_address)
|
75
|
+
@max_connect_tries = 10
|
76
|
+
else
|
77
|
+
@max_connect_tries = 1
|
78
|
+
end
|
79
|
+
@reconnect_timeout = 1
|
80
|
+
@next_reconnect_time = Time.utc(1980, 1, 1)
|
81
|
+
end
|
82
|
+
|
83
|
+
def connection
|
84
|
+
@mutex.synchronize do
|
85
|
+
@connection
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def clear_connection
|
90
|
+
@mutex.synchronize do
|
91
|
+
@connection.synchronize do
|
92
|
+
@random_dev = File.open('/dev/urandom') if @random_dev.closed?
|
93
|
+
@connection.unref
|
94
|
+
@connection = Connection.new(nil)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def close
|
100
|
+
@mutex.synchronize do
|
101
|
+
@connection.synchronize do
|
102
|
+
@random_dev.close
|
103
|
+
@connection.unref
|
104
|
+
@connection = nil
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def new_transaction(group_name, category, key)
|
110
|
+
if !@server_address
|
111
|
+
return Transaction.new(nil, nil)
|
112
|
+
elsif !group_name || group_name.empty?
|
113
|
+
raise ArgumentError, 'Group name may not be empty'
|
114
|
+
end
|
115
|
+
|
116
|
+
txn_id = create_txn_id
|
117
|
+
|
118
|
+
Lock.new(@mutex).synchronize do |_lock|
|
119
|
+
if Time.now < @next_reconnect_time
|
120
|
+
return Transaction.new(nil, nil)
|
121
|
+
end
|
122
|
+
|
123
|
+
Lock.new(@connection.mutex).synchronize do |connection_lock|
|
124
|
+
if !@connection.connected?
|
125
|
+
begin
|
126
|
+
connect
|
127
|
+
connection_lock.reset(@connection.mutex)
|
128
|
+
rescue SystemCallError, IOError
|
129
|
+
@connection.disconnect
|
130
|
+
UnionStationHooks::Log.warn(
|
131
|
+
"Cannot connect to the UstRouter at #{@server_address}; " \
|
132
|
+
"retrying in #{@reconnect_timeout} second(s).")
|
133
|
+
@next_reconnect_time = Time.now + @reconnect_timeout
|
134
|
+
return Transaction.new(nil, nil)
|
135
|
+
rescue Exception => e
|
136
|
+
@connection.disconnect
|
137
|
+
raise e
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
begin
|
142
|
+
@connection.channel.write('openTransaction',
|
143
|
+
txn_id, group_name, '', category,
|
144
|
+
Utils.encoded_timestamp,
|
145
|
+
key,
|
146
|
+
true,
|
147
|
+
true)
|
148
|
+
result = @connection.channel.read
|
149
|
+
if result[0] != 'status'
|
150
|
+
raise "Expected UstRouter to respond with 'status', " \
|
151
|
+
"but got #{result.inspect} instead"
|
152
|
+
elsif result[1] == 'ok'
|
153
|
+
# Do nothing
|
154
|
+
elsif result[1] == 'error'
|
155
|
+
if result[2]
|
156
|
+
raise "Unable to close transaction: #{result[2]}"
|
157
|
+
else
|
158
|
+
raise 'Unable to close transaction (no server message given)'
|
159
|
+
end
|
160
|
+
else
|
161
|
+
raise "Expected UstRouter to respond with 'ok' or 'error', " \
|
162
|
+
"but got #{result.inspect} instead"
|
163
|
+
end
|
164
|
+
|
165
|
+
return Transaction.new(@connection, txn_id)
|
166
|
+
rescue SystemCallError, IOError
|
167
|
+
@connection.disconnect
|
168
|
+
UnionStationHooks::Log.warn(
|
169
|
+
"The UstRouter at #{@server_address}" \
|
170
|
+
' closed the connection; will reconnect in ' \
|
171
|
+
"#{@reconnect_timeout} second(s).")
|
172
|
+
@next_reconnect_time = Time.now + @reconnect_timeout
|
173
|
+
return Transaction.new(nil, nil)
|
174
|
+
rescue Exception => e
|
175
|
+
@connection.disconnect
|
176
|
+
raise e
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def continue_transaction(txn_id, group_name, category, key)
|
183
|
+
if !@server_address
|
184
|
+
return Transaction.new(nil, nil)
|
185
|
+
elsif !txn_id || txn_id.empty?
|
186
|
+
raise ArgumentError, 'Transaction ID may not be empty'
|
187
|
+
end
|
188
|
+
|
189
|
+
Lock.new(@mutex).synchronize do |_lock|
|
190
|
+
if Time.now < @next_reconnect_time
|
191
|
+
return Transaction.new(nil, nil)
|
192
|
+
end
|
193
|
+
|
194
|
+
Lock.new(@connection.mutex).synchronize do |connection_lock|
|
195
|
+
if !@connection.connected?
|
196
|
+
begin
|
197
|
+
connect
|
198
|
+
connection_lock.reset(@connection.mutex)
|
199
|
+
rescue SystemCallError, IOError
|
200
|
+
@connection.disconnect
|
201
|
+
UnionStationHooks::Log.warn(
|
202
|
+
"Cannot connect to the UstRouter at #{@server_address}; " \
|
203
|
+
"retrying in #{@reconnect_timeout} second(s).")
|
204
|
+
@next_reconnect_time = Time.now + @reconnect_timeout
|
205
|
+
return Transaction.new(nil, nil)
|
206
|
+
rescue Exception => e
|
207
|
+
@connection.disconnect
|
208
|
+
raise e
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
begin
|
213
|
+
@connection.channel.write('openTransaction',
|
214
|
+
txn_id, group_name, '', category,
|
215
|
+
Utils.encoded_timestamp,
|
216
|
+
key,
|
217
|
+
true)
|
218
|
+
return Transaction.new(@connection, txn_id)
|
219
|
+
rescue SystemCallError, IOError
|
220
|
+
@connection.disconnect
|
221
|
+
UnionStationHooks::Log.warn(
|
222
|
+
"The UstRouter at #{@server_address}" \
|
223
|
+
' closed the connection; will reconnect in ' \
|
224
|
+
"#{@reconnect_timeout} second(s).")
|
225
|
+
@next_reconnect_time = Time.now + @reconnect_timeout
|
226
|
+
return Transaction.new(nil, nil)
|
227
|
+
rescue Exception => e
|
228
|
+
@connection.disconnect
|
229
|
+
raise e
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
private
|
236
|
+
|
237
|
+
RANDOM_CHARS = %w(
|
238
|
+
abcdefghijklmnopqrstuvwxyz
|
239
|
+
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
240
|
+
0123456789
|
241
|
+
)
|
242
|
+
|
243
|
+
def connect
|
244
|
+
socket = connect_to_server(@server_address)
|
245
|
+
channel = MessageChannel.new(socket)
|
246
|
+
|
247
|
+
handshake_version(channel)
|
248
|
+
handshake_authentication(channel)
|
249
|
+
handshake_initialization(channel)
|
250
|
+
|
251
|
+
@connection.unref
|
252
|
+
@connection = Connection.new(socket)
|
253
|
+
rescue Exception => e
|
254
|
+
socket.close if socket && !socket.closed?
|
255
|
+
raise e
|
256
|
+
end
|
257
|
+
|
258
|
+
def handshake_version(channel)
|
259
|
+
result = channel.read
|
260
|
+
if result.nil?
|
261
|
+
raise EOFError
|
262
|
+
elsif result.size != 2 || result[0] != 'version'
|
263
|
+
raise IOError, "The UstRouter didn't sent a valid version identifier"
|
264
|
+
elsif result[1] != '1'
|
265
|
+
raise IOError, "Unsupported UstRouter protocol version #{result[1]}"
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def handshake_authentication(channel)
|
270
|
+
channel.write_scalar(@username)
|
271
|
+
channel.write_scalar(@password)
|
272
|
+
process_ust_router_reply(channel,
|
273
|
+
'UstRouter client authentication error',
|
274
|
+
SecurityError)
|
275
|
+
end
|
276
|
+
|
277
|
+
def handshake_initialization(channel)
|
278
|
+
channel.write('init', @node_name)
|
279
|
+
process_ust_router_reply(channel,
|
280
|
+
'UstRouter client initialization error')
|
281
|
+
end
|
282
|
+
|
283
|
+
def random_token(length)
|
284
|
+
token = ''
|
285
|
+
@random_dev.read(length).each_byte do |c|
|
286
|
+
token << RANDOM_CHARS[c % RANDOM_CHARS.size]
|
287
|
+
end
|
288
|
+
token
|
289
|
+
end
|
290
|
+
|
291
|
+
def create_txn_id
|
292
|
+
result = (Time.now.to_i / 60).to_s(36)
|
293
|
+
result << "-#{random_token(11)}"
|
294
|
+
result
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|