freeswitch-esl 0.2.0 → 0.2.2
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 +4 -4
- data/README.md +17 -0
- data/lib/freeswitch/esl/command.rb +48 -15
- data/lib/freeswitch/esl/connection/command_dispatcher.rb +13 -5
- data/lib/freeswitch/esl/connection.rb +8 -5
- data/lib/freeswitch/esl/version.rb +1 -1
- metadata +8 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a8cd34473eb85fec06aef418a2560d26a64975cb08245de7776aae2678dea90e
|
|
4
|
+
data.tar.gz: e79323aad3dd7655ea46c731818edbbf056dc6c24fb051fedc26a46327133231
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 106d51156de70d8fcd97dc9c1d2f1f4c8d748b3c107f0967a23c40627dbcdd4d7a86d146c91b7a30e6e11913932b17c5e7470f5ef0d0651e48d49b332a802ac2
|
|
7
|
+
data.tar.gz: 4a4d954236148455f75d44518e9c08e8f17f430d786bba00332cb724920788af46e361b40c5d4421f9acfc95dc46e2e7b78a0341c08e813669542f09f9aa1348
|
data/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# freeswitch-esl
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
[](https://badge.fury.io/rb/freeswitch-esl)
|
|
5
|
+
|
|
6
|
+
|
|
3
7
|
`freeswitch-esl` is a Ruby gem for interacting with FreeSWITCH through ESL (Event Socket Library).
|
|
4
8
|
This version is intentionally focused on one direction only: your Ruby process connects inbound to `mod_event_socket` and manages one or more `Freeswitch::ESL::Client` instances.
|
|
5
9
|
|
|
@@ -179,6 +183,16 @@ You can:
|
|
|
179
183
|
- inspect `command.status`, `command.ok?`, `command.failed?`, `command.timeout?`
|
|
180
184
|
- attach `command.on_complete { |cmd| ... }` for async completion handling
|
|
181
185
|
|
|
186
|
+
As of 0.2.2, `client.exec(..., timeout: seconds)` shares one monotonic timeout
|
|
187
|
+
budget across client readiness, the initial `bgapi` acknowledgement and the
|
|
188
|
+
`BACKGROUND_JOB` result. `exec` waits for that acknowledgement; `wait`/`response`
|
|
189
|
+
wait for the result. `timeout: nil` disables the deadline. Submission timeout
|
|
190
|
+
raises `Freeswitch::ESL::TimeoutError` unless `raise_error: false`, in which case
|
|
191
|
+
the command records `status: :timeout`. Late replies are consumed in FIFO order
|
|
192
|
+
without reviving the command or closing the connection. A timeout after sending
|
|
193
|
+
does **not** prove that FreeSWITCH did not execute the command; reconcile its
|
|
194
|
+
remote outcome before deciding whether a retry is safe.
|
|
195
|
+
|
|
182
196
|
If you prefer lower-level primitives, `Freeswitch::ESL::Client` also exposes the inherited connection API:
|
|
183
197
|
|
|
184
198
|
- `send_command`
|
|
@@ -215,6 +229,9 @@ client.subscribe('CHANNEL_HANGUP')
|
|
|
215
229
|
|
|
216
230
|
`client.close` stops reconnect attempts immediately. The same client instance can be
|
|
217
231
|
connected again later with `client.connect`.
|
|
232
|
+
Call `close` for cleanup even after a remote disconnect: it releases the event
|
|
233
|
+
dispatcher too. Local shutdown fails pending command replies with
|
|
234
|
+
`Freeswitch::ESL::DisconnectedError` and does not trigger a reconnect.
|
|
218
235
|
|
|
219
236
|
## Docker Compose Test Lab
|
|
220
237
|
|
|
@@ -22,9 +22,10 @@ module Freeswitch
|
|
|
22
22
|
#
|
|
23
23
|
# === Timeout
|
|
24
24
|
#
|
|
25
|
-
# The default timeout is {DEFAULT_TIMEOUT} seconds
|
|
26
|
-
#
|
|
27
|
-
#
|
|
25
|
+
# The default timeout is {DEFAULT_TIMEOUT} seconds across client readiness,
|
|
26
|
+
# the bgapi acknowledgement and the result event. A timeout after submission
|
|
27
|
+
# does not establish whether FreeSWITCH executed the command. Late replies
|
|
28
|
+
# remain in the connection FIFO until consumed; late results are ignored.
|
|
28
29
|
class Command
|
|
29
30
|
DEFAULT_TIMEOUT = 60
|
|
30
31
|
|
|
@@ -61,17 +62,13 @@ module Freeswitch
|
|
|
61
62
|
def execute!
|
|
62
63
|
configure_timeout!
|
|
63
64
|
return unless wait_until_client_ready
|
|
65
|
+
return unless begin_submission?
|
|
64
66
|
|
|
65
|
-
@
|
|
66
|
-
@status = :sent
|
|
67
|
-
@job_uuid = @client.bgapi(@command, *@args) do |event|
|
|
67
|
+
@job_uuid = @client.bgapi(@command, *@args, timeout: remaining_timeout) do |event|
|
|
68
68
|
event.error? ? fail!(event) : complete!(event)
|
|
69
69
|
end
|
|
70
70
|
rescue StandardError => e
|
|
71
|
-
|
|
72
|
-
@error = e
|
|
73
|
-
@finished_at = now
|
|
74
|
-
raise e if @raise_error
|
|
71
|
+
handle_submission_error(e)
|
|
75
72
|
end
|
|
76
73
|
|
|
77
74
|
# Register a callback to be called when the command completes (success or
|
|
@@ -82,7 +79,7 @@ module Freeswitch
|
|
|
82
79
|
raise ArgumentError, "Block is required" unless block_given?
|
|
83
80
|
|
|
84
81
|
@callbacks << block
|
|
85
|
-
|
|
82
|
+
yield(self) if completed?
|
|
86
83
|
|
|
87
84
|
self
|
|
88
85
|
end
|
|
@@ -104,10 +101,14 @@ module Freeswitch
|
|
|
104
101
|
@mutex.synchronize do
|
|
105
102
|
return self if completed?
|
|
106
103
|
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
while pending?
|
|
105
|
+
break if deadline_expired?
|
|
106
|
+
|
|
107
|
+
@cond.wait(@mutex, remaining_timeout)
|
|
108
|
+
end
|
|
109
109
|
end
|
|
110
110
|
|
|
111
|
+
timeout!("Command timed out after #{@timeout}s waiting for completion") if pending?
|
|
111
112
|
raise @error if @error && @raise_error
|
|
112
113
|
|
|
113
114
|
self
|
|
@@ -157,16 +158,48 @@ module Freeswitch
|
|
|
157
158
|
def configure_timeout!
|
|
158
159
|
return unless @timeout
|
|
159
160
|
|
|
161
|
+
@deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @timeout
|
|
160
162
|
Ztimer.after(@timeout * 1000) do
|
|
161
|
-
timeout!("Command timed out after #{@timeout}s waiting for
|
|
163
|
+
timeout!("Command timed out after #{@timeout}s waiting for completion")
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def remaining_timeout
|
|
168
|
+
[@deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC), 0].max if @deadline
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def deadline_expired?
|
|
172
|
+
@deadline && remaining_timeout.zero?
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# The watchdog may already have settled the command while bgapi was
|
|
176
|
+
# waiting. Never overwrite that result or invoke completion twice.
|
|
177
|
+
def handle_submission_error(error)
|
|
178
|
+
settle(error.is_a?(TimeoutError) ? :timeout : :failed) { @error = error }
|
|
179
|
+
raise @error if @raise_error && @error
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def begin_submission?
|
|
183
|
+
@mutex.synchronize do
|
|
184
|
+
return false unless pending?
|
|
185
|
+
|
|
186
|
+
if !@deadline || remaining_timeout.positive?
|
|
187
|
+
@sent_at = now
|
|
188
|
+
@status = :sent
|
|
189
|
+
@cond.broadcast
|
|
190
|
+
return true
|
|
191
|
+
end
|
|
162
192
|
end
|
|
193
|
+
|
|
194
|
+
timeout!("Command timed out after #{@timeout}s waiting for client readiness")
|
|
195
|
+
false
|
|
163
196
|
end
|
|
164
197
|
|
|
165
198
|
# Wait until the client is ready to send commands. If readiness does not
|
|
166
199
|
# happen within the configured timeout, the command fails before any bgapi
|
|
167
200
|
# request is sent to FreeSWITCH.
|
|
168
201
|
def wait_until_client_ready
|
|
169
|
-
is_ready = @client.wait_until_ready(timeout:
|
|
202
|
+
is_ready = @client.wait_until_ready(timeout: remaining_timeout, raise_error: false).ready?
|
|
170
203
|
return is_ready if is_ready
|
|
171
204
|
|
|
172
205
|
settle(:failed) do
|
|
@@ -100,6 +100,8 @@ module Freeswitch
|
|
|
100
100
|
|
|
101
101
|
logger.debug "Stopping command dispatcher and closing socket"
|
|
102
102
|
@closed = true
|
|
103
|
+
@disconnect_error ||= DisconnectedError.new("Connection is closed")
|
|
104
|
+
disconnect_pending_commands
|
|
103
105
|
@healthcheck_timer&.cancel!
|
|
104
106
|
|
|
105
107
|
begin
|
|
@@ -140,19 +142,25 @@ module Freeswitch
|
|
|
140
142
|
# pending command with the same error and then invoke the disconnect
|
|
141
143
|
# callback once teardown has completed.
|
|
142
144
|
def disconnect!(error)
|
|
145
|
+
# Closing the socket in stop wakes a blocked reader with EOF/IOError.
|
|
146
|
+
# That is local teardown, not a new remote disconnect/reconnect signal.
|
|
147
|
+
return if closed?
|
|
148
|
+
|
|
143
149
|
@disconnect_error = error
|
|
144
150
|
logger.debug "Disconnected: #{error.message}"
|
|
145
151
|
|
|
146
|
-
|
|
152
|
+
stop
|
|
153
|
+
@on_disconnect&.call(error)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Local teardown must wake pending commands too, without triggering reconnect.
|
|
157
|
+
def disconnect_pending_commands
|
|
147
158
|
loop do
|
|
148
159
|
cmd = pop_command(non_block: true)
|
|
149
160
|
break unless cmd
|
|
150
161
|
|
|
151
|
-
cmd.disconnected!(
|
|
162
|
+
cmd.disconnected!(@disconnect_error)
|
|
152
163
|
end
|
|
153
|
-
|
|
154
|
-
stop
|
|
155
|
-
@on_disconnect&.call(error)
|
|
156
164
|
end
|
|
157
165
|
|
|
158
166
|
# Enqueue and write the command while the caller holds +@write_mutex+ so
|
|
@@ -99,14 +99,14 @@ module Freeswitch
|
|
|
99
99
|
# removed together with the dispatcher instances, so a reconnect path must
|
|
100
100
|
# rebuild them through {#initialize_socket}.
|
|
101
101
|
def close
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
# A remote disconnect sets @closed before explicit cleanup. Dispatchers
|
|
103
|
+
# still need to be stopped even when the transport is already closed.
|
|
104
104
|
@closed = true
|
|
105
105
|
@command_dispatcher&.stop
|
|
106
106
|
@event_dispatcher&.stop
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
@command_dispatcher = nil
|
|
109
|
+
@event_dispatcher = nil
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
protected
|
|
@@ -114,12 +114,15 @@ module Freeswitch
|
|
|
114
114
|
# (Re-)initialise all per-connection state for the given socket.
|
|
115
115
|
# Called by subclasses on initial connect and on reconnect.
|
|
116
116
|
def initialize_socket(socket, debug: false)
|
|
117
|
+
# Failed authentication/subscription may leave the previous reader alive.
|
|
118
|
+
# Retire it before publishing a replacement dispatcher.
|
|
119
|
+
@command_dispatcher&.stop
|
|
117
120
|
@socket = socket
|
|
118
121
|
@closed = false
|
|
119
122
|
|
|
120
123
|
# Keep dispatcher data across reconnects so existing handlers still work
|
|
121
124
|
# after a new socket is created.
|
|
122
|
-
unless
|
|
125
|
+
unless @event_dispatcher
|
|
123
126
|
@event_dispatcher = EventDispatcher.new
|
|
124
127
|
@event_dispatcher.start
|
|
125
128
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: freeswitch-esl
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Demetra Opinioni.net Srl
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: configatron
|
|
@@ -151,9 +150,9 @@ dependencies:
|
|
|
151
150
|
- !ruby/object:Gem::Version
|
|
152
151
|
version: '0.22'
|
|
153
152
|
description: |
|
|
154
|
-
A thread-safe Ruby client for
|
|
155
|
-
|
|
156
|
-
|
|
153
|
+
A thread-safe Ruby client for FreeSWITCH Event Socket Library connections.
|
|
154
|
+
Focused on inbound mod_event_socket integration with command execution,
|
|
155
|
+
bgapi jobs, JSON event handling, and reconnect support.
|
|
157
156
|
email:
|
|
158
157
|
- developers@opinioni.net
|
|
159
158
|
executables: []
|
|
@@ -175,12 +174,11 @@ files:
|
|
|
175
174
|
- lib/freeswitch/esl/protocol/event.rb
|
|
176
175
|
- lib/freeswitch/esl/protocol/message.rb
|
|
177
176
|
- lib/freeswitch/esl/version.rb
|
|
178
|
-
homepage: https://gitlab.opinioni.net/demetra-
|
|
177
|
+
homepage: https://gitlab.opinioni.net/demetra-developers/rubygems/freeswitch-esl
|
|
179
178
|
licenses:
|
|
180
179
|
- MIT
|
|
181
180
|
metadata:
|
|
182
181
|
rubygems_mfa_required: 'true'
|
|
183
|
-
post_install_message:
|
|
184
182
|
rdoc_options: []
|
|
185
183
|
require_paths:
|
|
186
184
|
- lib
|
|
@@ -195,8 +193,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
195
193
|
- !ruby/object:Gem::Version
|
|
196
194
|
version: '0'
|
|
197
195
|
requirements: []
|
|
198
|
-
rubygems_version:
|
|
199
|
-
signing_key:
|
|
196
|
+
rubygems_version: 4.0.16
|
|
200
197
|
specification_version: 4
|
|
201
|
-
summary: Ruby client for FreeSWITCH
|
|
198
|
+
summary: Inbound Ruby client for FreeSWITCH ESL
|
|
202
199
|
test_files: []
|