adhearsion 2.0.1 → 2.1.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.
Files changed (62) hide show
  1. data/.travis.yml +4 -3
  2. data/CHANGELOG.md +30 -0
  3. data/README.markdown +1 -0
  4. data/adhearsion.gemspec +3 -4
  5. data/bin/ahn +0 -20
  6. data/features/cli_create.feature +1 -1
  7. data/features/cli_restart.feature +25 -1
  8. data/features/cli_start.feature +0 -2
  9. data/features/plugin_generator.feature +66 -15
  10. data/features/support/env.rb +0 -13
  11. data/lib/adhearsion.rb +26 -6
  12. data/lib/adhearsion/call.rb +42 -7
  13. data/lib/adhearsion/call_controller.rb +5 -2
  14. data/lib/adhearsion/call_controller/dial.rb +92 -50
  15. data/lib/adhearsion/call_controller/input.rb +19 -6
  16. data/lib/adhearsion/call_controller/menu_dsl/menu.rb +4 -0
  17. data/lib/adhearsion/call_controller/output.rb +143 -161
  18. data/lib/adhearsion/call_controller/output/abstract_player.rb +30 -0
  19. data/lib/adhearsion/call_controller/output/async_player.rb +26 -0
  20. data/lib/adhearsion/call_controller/output/formatter.rb +81 -0
  21. data/lib/adhearsion/call_controller/output/player.rb +25 -0
  22. data/lib/adhearsion/call_controller/record.rb +19 -2
  23. data/lib/adhearsion/events.rb +3 -0
  24. data/lib/adhearsion/foundation.rb +12 -6
  25. data/lib/adhearsion/foundation/exception_handler.rb +8 -6
  26. data/lib/adhearsion/generators/app/templates/README.md +13 -0
  27. data/lib/adhearsion/generators/app/templates/config/adhearsion.rb +7 -1
  28. data/lib/adhearsion/generators/plugin/plugin_generator.rb +1 -0
  29. data/lib/adhearsion/generators/plugin/templates/plugin-template.gemspec.tt +3 -7
  30. data/lib/adhearsion/generators/plugin/templates/spec/spec_helper.rb.tt +0 -1
  31. data/lib/adhearsion/outbound_call.rb +15 -5
  32. data/lib/adhearsion/punchblock_plugin.rb +13 -2
  33. data/lib/adhearsion/punchblock_plugin/initializer.rb +13 -12
  34. data/lib/adhearsion/router.rb +43 -2
  35. data/lib/adhearsion/router/evented_route.rb +15 -0
  36. data/lib/adhearsion/router/openended_route.rb +16 -0
  37. data/lib/adhearsion/router/route.rb +31 -13
  38. data/lib/adhearsion/router/unaccepting_route.rb +11 -0
  39. data/lib/adhearsion/version.rb +1 -1
  40. data/pre-commit +14 -1
  41. data/spec/adhearsion/call_controller/dial_spec.rb +105 -10
  42. data/spec/adhearsion/call_controller/input_spec.rb +19 -21
  43. data/spec/adhearsion/call_controller/output/async_player_spec.rb +67 -0
  44. data/spec/adhearsion/call_controller/output/formatter_spec.rb +90 -0
  45. data/spec/adhearsion/call_controller/output/player_spec.rb +65 -0
  46. data/spec/adhearsion/call_controller/output_spec.rb +436 -190
  47. data/spec/adhearsion/call_controller/record_spec.rb +49 -6
  48. data/spec/adhearsion/call_controller_spec.rb +10 -2
  49. data/spec/adhearsion/call_spec.rb +138 -0
  50. data/spec/adhearsion/calls_spec.rb +1 -1
  51. data/spec/adhearsion/outbound_call_spec.rb +48 -8
  52. data/spec/adhearsion/punchblock_plugin/initializer_spec.rb +34 -23
  53. data/spec/adhearsion/router/evented_route_spec.rb +34 -0
  54. data/spec/adhearsion/router/openended_route_spec.rb +61 -0
  55. data/spec/adhearsion/router/route_spec.rb +26 -4
  56. data/spec/adhearsion/router/unaccepting_route_spec.rb +72 -0
  57. data/spec/adhearsion/router_spec.rb +107 -2
  58. data/spec/adhearsion_spec.rb +19 -0
  59. data/spec/capture_warnings.rb +28 -21
  60. data/spec/spec_helper.rb +2 -3
  61. data/spec/support/call_controller_test_helpers.rb +31 -30
  62. metadata +32 -29
@@ -1,10 +1,11 @@
1
1
  language: ruby
2
+ script: bundle exec rake --trace
2
3
  rvm:
3
4
  - 1.9.2
4
5
  - 1.9.3
5
- - jruby-19mode # JRuby in 1.9 mode
6
- - rbx-19mode # currently in active development, may or may not work for your project
7
- - ruby-head
6
+ # - jruby-19mode # JRuby in 1.9 mode
7
+ # - rbx-19mode # currently in active development, may or may not work for your project
8
+ # - ruby-head
8
9
  env: ARUBA_TIMEOUT=120 RAILS_ENV=development AHN_ENV=development
9
10
  notifications:
10
11
  irc: "irc.freenode.org#adhearsion"
@@ -1,5 +1,35 @@
1
1
  # [develop](https://github.com/adhearsion/adhearsion)
2
2
 
3
+ # [2.1.0](https://github.com/adhearsion/adhearsion/compare/v2.0.1...v2.1.0) - [2012-08-07](https://rubygems.org/gems/adhearsion/versions/2.1.0)
4
+
5
+ ## Features
6
+ * Initial support for FreeSWITCH
7
+ * Added the possibility to specify a confirmation controller on `#dial` operations
8
+ * Allow specifying a controller to run when originating an outbound call
9
+ * Allow `Call#execute_controller` to take a block instead of a controller instance. Simplifies event-based execution of simple controllers (eg whisper into a call)
10
+ * Allow route modifiers such that they:
11
+ * Do not accept calls that match
12
+ * Do not execute a controller
13
+ * Do not hangup after controller execution
14
+ * Permit asynchronous output using bang version of methods (eg `CallController#play!`), returning an output component, which can be stopped
15
+ * Added `CallController#safely` which will catch and log `StandardError` in a call controller, but will not allow it to crash the controller
16
+ * `CallController#record` now has an `:interruptible` option that allows recording to be stopped by pressing any DTMF key
17
+ * Added `Call#on_joined` and `Call#on_unjoined` for easily registering joined/unjoined handlers
18
+ * `Adhearsion.root` and `Adhearsion.root=` are now available to return the root path to the application. `Adhearsion.ahn_root=` is deprecated
19
+ * `Adhearsion.deprecated` added for internal use to clearly mark deprecated methods
20
+
21
+ ## Bugfixes
22
+ * All output methods will now raise `Adhearsion::CallController::Output::PlaybackError` when output fails, instead of failing silently
23
+ * `CallController#hangup` now prevents further execution of the controller
24
+ * Calls which do not match any routes are rejected with an error
25
+ * Calls are not accepted until a matching route is found
26
+ * Give sensible dependency defaults for generated plugins
27
+ * Fixed mocha-fail in generated plugins
28
+ * Plugins generated with a snake_case name did not have the appropriate constants camelized
29
+ * `CallController#dial` no longer creates outbound calls if the dialing party hangs up before it executes
30
+ * `CallController#ask` no longer loops on timeout
31
+ * Correct default port for Asterisk
32
+
3
33
  # [2.0.1](https://github.com/adhearsion/adhearsion/compare/v2.0.0...v2.0.1) - [2012-06-04](https://rubygems.org/gems/adhearsion/versions/2.0.1)
4
34
  * Bugfix: Avoid infinitely recursive exception handlers
5
35
  * Bugfix: Don't require rubygems where we don't need it
@@ -20,6 +20,7 @@ Adhearsion rests above a lower-level telephony platform, for example [Asterisk](
20
20
  * Ruby 1.9.2+ or JRuby 1.6.7+
21
21
  * A VoIP platform:
22
22
  * Asterisk 1.8+
23
+ * FreeSWITCH
23
24
  * Prism 11+ with rayo-server
24
25
  * An interest in building cool new things
25
26
 
@@ -30,19 +30,18 @@ Gem::Specification.new do |s|
30
30
  s.add_runtime_dependency 'jruby-openssl' if RUBY_PLATFORM == 'java'
31
31
  s.add_runtime_dependency 'logging', [">= 1.6.1"]
32
32
  s.add_runtime_dependency 'pry'
33
- s.add_runtime_dependency 'punchblock', ["~> 1.0"]
33
+ s.add_runtime_dependency 'punchblock', ["~> 1.4"]
34
34
  s.add_runtime_dependency 'rake'
35
35
  s.add_runtime_dependency 'ruby_speech', ["~> 1.0"]
36
36
  s.add_runtime_dependency 'thor'
37
- s.add_runtime_dependency 'uuid'
38
37
 
39
- s.add_development_dependency 'aruba'
38
+ s.add_development_dependency 'aruba', "~> 0.4"
40
39
  s.add_development_dependency 'ci_reporter'
41
40
  s.add_development_dependency 'cucumber'
42
41
  s.add_development_dependency 'flexmock'
43
42
  s.add_development_dependency 'guard-cucumber'
44
43
  s.add_development_dependency 'guard-rspec'
45
- s.add_development_dependency 'rspec', ["~> 2.7.0"]
44
+ s.add_development_dependency 'rspec', ["~> 2.11.0"]
46
45
  s.add_development_dependency 'ruby_gntp'
47
46
  s.add_development_dependency 'simplecov'
48
47
  s.add_development_dependency 'simplecov-rcov'
data/bin/ahn CHANGED
@@ -1,24 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # This is the main executable file.
4
-
5
- # Adhearsion, open source collaboration framework
6
- # Copyright (C) 2006,2007,2008 Jay Phillips
7
- #
8
- # This library is free software; you can redistribute it and/or modify it under
9
- # the terms of the GNU Lesser General Public License as published by the Free
10
- # Software Foundation; either version 2.1 of the License, or (at your option)
11
- # any later version.
12
- #
13
- # This library is distributed in the hope that it will be useful, but WITHOUT
14
- # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15
- # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
16
- # details.
17
- #
18
- # You should have received a copy of the GNU Lesser General Public License along
19
- # with this library; if not, write to the Free Software Foundation, Inc.,
20
- # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
-
22
3
  require 'adhearsion/cli'
23
-
24
4
  Adhearsion::CLI::AhnCommand.start
@@ -19,6 +19,6 @@ Feature: Adhearsion Ahn CLI (Create)
19
19
  When I run `ahn create`
20
20
  Then the output should contain:
21
21
  """
22
- "create" was called incorrectly. Call as "ahn create /path/to/directory".
22
+ ahn create requires at least 1 argument: "ahn create /path/to/directory".
23
23
  """
24
24
  And the exit status should be 1
@@ -1,4 +1,4 @@
1
- Feature: Adhearsion Ahn CLI (stop)
1
+ Feature: Adhearsion Ahn CLI (restart)
2
2
  As an Adhearsion user
3
3
  I want the ahn command to provide a 'restart' command
4
4
  So that I can restart a running Adhearsion daemon
@@ -17,3 +17,27 @@ Feature: Adhearsion Ahn CLI (stop)
17
17
  """
18
18
  Starting Adhearsion
19
19
  """
20
+
21
+ @reconnect
22
+ Scenario: Command restart with no path outside of the app directory
23
+ Given that I create a valid app under "path/somewhere"
24
+ When I run `ahn daemon path/somewhere --pid-file=ahn.pid`
25
+ And I run `ahn restart --pid-file=ahn.pid`
26
+ Then the output should contain:
27
+ """
28
+ A valid path is required for restart, unless run from an Adhearson app directory
29
+ """
30
+ And the exit status should be 1
31
+
32
+ @reconnect
33
+ Scenario: Command restart with no path inside of the app directory
34
+ Given JRuby skip test
35
+ Given that I create a valid app under "path/somewhere"
36
+ When I cd to "path/somewhere"
37
+ And I run `ahn daemon --pid-file=ahn.pid`
38
+ And I run `ahn restart --pid-file=ahn.pid`
39
+ Then the output should contain:
40
+ """
41
+ Starting Adhearsion
42
+ """
43
+ And the exit status should be 0
@@ -18,7 +18,6 @@ Feature: Adhearsion Ahn CLI (start)
18
18
  And I run `ahn start` interactively
19
19
  And I wait for output to contain "Starting connection to server"
20
20
  Then the output should contain "Adhearsion::Console: Launching Adhearsion Console"
21
- And the output should contain "AHN>"
22
21
  And the output should contain "Adhearsion shut down"
23
22
 
24
23
  Scenario: Command start with only path works properly
@@ -27,5 +26,4 @@ Feature: Adhearsion Ahn CLI (start)
27
26
  When I run `ahn start path/somewhere` interactively
28
27
  And I wait for output to contain "Starting connection to server"
29
28
  Then the output should contain "Adhearsion::Console: Launching Adhearsion Console"
30
- And the output should contain "AHN>"
31
29
  And the output should contain "Adhearsion shut down"
@@ -3,29 +3,80 @@ Feature: Adhearsion plugin generator
3
3
  As an Adhearsion plugin developer
4
4
  I want to generate a plugin and its basic structure
5
5
 
6
- Scenario: Generate the basic structure for a plugin
6
+ Scenario: Generate the basic structure for a plugin with a constant name
7
7
  When I run `ahn generate plugin TestPlugin`
8
8
  Then the following directories should exist:
9
- | test_plugin |
10
- | test_plugin/lib |
11
- | test_plugin/lib/test_plugin |
12
- | test_plugin/spec |
9
+ | test_plugin |
10
+ | test_plugin/lib |
11
+ | test_plugin/lib/test_plugin |
12
+ | test_plugin/spec |
13
13
  And the following files should exist:
14
- | test_plugin/test_plugin.gemspec |
15
- | test_plugin/Rakefile |
16
- | test_plugin/README.md |
17
- | test_plugin/Gemfile |
18
- | test_plugin/lib/test_plugin.rb |
19
- | test_plugin/lib/test_plugin/version.rb |
20
- | test_plugin/lib/test_plugin/plugin.rb |
21
- | test_plugin/lib/test_plugin/controller_methods.rb |
22
- | test_plugin/spec/spec_helper.rb |
23
- | test_plugin/spec/test_plugin/controller_methods_spec.rb |
14
+ | test_plugin/test_plugin.gemspec |
15
+ | test_plugin/Rakefile |
16
+ | test_plugin/README.md |
17
+ | test_plugin/Gemfile |
18
+ | test_plugin/lib/test_plugin.rb |
19
+ | test_plugin/lib/test_plugin/version.rb |
20
+ | test_plugin/lib/test_plugin/plugin.rb |
21
+ | test_plugin/lib/test_plugin/controller_methods.rb |
22
+ | test_plugin/spec/spec_helper.rb |
23
+ | test_plugin/spec/test_plugin/controller_methods_spec.rb |
24
24
  And the file "test_plugin/test_plugin.gemspec" should contain "test_plugin/version"
25
25
  And the file "test_plugin/README.md" should contain "TestPlugin"
26
26
  And the file "test_plugin/lib/test_plugin.rb" should contain each of these content parts:
27
27
  """
28
+ TestPlugin
29
+ test_plugin/version
30
+ test_plugin/plugin
31
+ test_plugin/controller_methods
32
+ """
33
+ And the file "test_plugin/lib/test_plugin/version.rb" should contain each of these content parts:
34
+ """
28
35
  module TestPlugin
36
+ VERSION
37
+ """
38
+ And the file "test_plugin/lib/test_plugin/plugin.rb" should contain each of these content parts:
39
+ """
40
+ module TestPlugin
41
+ init :test_plugin
42
+ config :test_plugin
43
+ namespace :test_plugin
44
+ """
45
+ And the file "test_plugin/lib/test_plugin/controller_methods.rb" should contain each of these content parts:
46
+ """
47
+ module TestPlugin
48
+ def greet
49
+ """
50
+ And the file "test_plugin/spec/spec_helper.rb" should contain "require 'test_plugin'"
51
+ And the file "test_plugin/spec/test_plugin/controller_methods_spec.rb" should contain each of these content parts:
52
+ """
53
+ module TestPlugin
54
+ include TestPlugin::ControllerMethods
55
+ """
56
+
57
+ Scenario: Generate the basic structure for a plugin with an underscored name
58
+ When I run `ahn generate plugin test_plugin`
59
+ Then the following directories should exist:
60
+ | test_plugin |
61
+ | test_plugin/lib |
62
+ | test_plugin/lib/test_plugin |
63
+ | test_plugin/spec |
64
+ And the following files should exist:
65
+ | test_plugin/test_plugin.gemspec |
66
+ | test_plugin/Rakefile |
67
+ | test_plugin/README.md |
68
+ | test_plugin/Gemfile |
69
+ | test_plugin/lib/test_plugin.rb |
70
+ | test_plugin/lib/test_plugin/version.rb |
71
+ | test_plugin/lib/test_plugin/plugin.rb |
72
+ | test_plugin/lib/test_plugin/controller_methods.rb |
73
+ | test_plugin/spec/spec_helper.rb |
74
+ | test_plugin/spec/test_plugin/controller_methods_spec.rb |
75
+ And the file "test_plugin/test_plugin.gemspec" should contain "test_plugin/version"
76
+ And the file "test_plugin/README.md" should contain "TestPlugin"
77
+ And the file "test_plugin/lib/test_plugin.rb" should contain each of these content parts:
78
+ """
79
+ TestPlugin
29
80
  test_plugin/version
30
81
  test_plugin/plugin
31
82
  test_plugin/controller_methods
@@ -1,18 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'simplecov'
4
- require 'simplecov-rcov'
5
- class SimpleCov::Formatter::MergedFormatter
6
- def format(result)
7
- SimpleCov::Formatter::HTMLFormatter.new.format(result)
8
- SimpleCov::Formatter::RcovFormatter.new.format(result)
9
- end
10
- end
11
- SimpleCov.formatter = SimpleCov::Formatter::MergedFormatter
12
- SimpleCov.start do
13
- add_filter "/vendor/"
14
- end
15
-
16
3
  JRUBY_OPTS_SAVED=ENV['JRUBY_OPTS']
17
4
  JAVA_OPTS_SAVED=ENV['JAVA_OPTS']
18
5
 
@@ -4,14 +4,9 @@ abort "ERROR: You are running Adhearsion on an unsupported version of Ruby (Ruby
4
4
 
5
5
  %w{
6
6
  active_support/all
7
- uuid
8
- future-resource
9
7
  punchblock
10
- ostruct
11
8
  ruby_speech
12
9
  countdownlatch
13
- has_guarded_handlers
14
- girl_friday
15
10
  loquacious
16
11
  celluloid
17
12
 
@@ -42,16 +37,41 @@ module Adhearsion
42
37
 
43
38
  class << self
44
39
 
45
- def ahn_root=(path)
40
+ #
41
+ # Sets the application path
42
+ # @param[String|Pathname] The application path to set
43
+ #
44
+ def root=(path)
46
45
  Adhearsion.config[:platform].root = path.nil? ? nil : File.expand_path(path)
47
46
  end
48
47
 
48
+ #
49
+ # Returns the current application path
50
+ # @return [Pathname] The application path
51
+ #
52
+ def root
53
+ Adhearsion.config[:platform].root
54
+ end
55
+
56
+ #
57
+ # @deprecated Use #root= instead
58
+ #
59
+ def ahn_root=(path)
60
+ Adhearsion.deprecated "#Adhearsion.root="
61
+ Adhearsion.root = path
62
+ end
63
+
49
64
  def config(&block)
50
65
  @config ||= initialize_config
51
66
  block_given? and yield @config
52
67
  @config
53
68
  end
54
69
 
70
+ def deprecated(new_method)
71
+ logger.info "#{caller[0]} - This method is deprecated, please use #{new_method}."
72
+ logger.warn caller.join("\n")
73
+ end
74
+
55
75
  def initialize_config
56
76
  _config = Configuration.new
57
77
  env = ENV['AHN_ENV'] || ENV['RAILS_ENV']
@@ -1,5 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
+ require 'has_guarded_handlers'
3
4
  require 'thread'
4
5
 
5
6
  module Adhearsion
@@ -114,16 +115,14 @@ module Adhearsion
114
115
  throw :pass
115
116
  end
116
117
 
117
- register_event_handler Punchblock::Event::Joined do |event|
118
+ on_joined do |event|
118
119
  target = event.call_id || event.mixer_name
119
120
  signal :joined, target
120
- throw :pass
121
121
  end
122
122
 
123
- register_event_handler Punchblock::Event::Unjoined do |event|
123
+ on_unjoined do |event|
124
124
  target = event.call_id || event.mixer_name
125
125
  signal :unjoined, target
126
- throw :pass
127
126
  end
128
127
 
129
128
  on_end do |event|
@@ -132,6 +131,7 @@ module Adhearsion
132
131
  @end_reason = event.reason
133
132
  commands.terminate
134
133
  after(after_end_hold_time) { current_actor.terminate! }
134
+ throw :pass
135
135
  end
136
136
  end
137
137
 
@@ -140,6 +140,39 @@ module Adhearsion
140
140
  30
141
141
  end
142
142
 
143
+ ##
144
+ # Registers a callback for when this call is joined to another call or a mixer
145
+ #
146
+ # @param [Call, String, Hash, nil] target the target to guard on. May be a Call object, a call ID (String, Hash) or a mixer name (Hash)
147
+ # @option target [String] call_id The call ID to guard on
148
+ # @option target [String] mixer_name The mixer name to guard on
149
+ #
150
+ def on_joined(target = nil, &block)
151
+ register_event_handler Punchblock::Event::Joined, *guards_for_target(target) do |event|
152
+ block.call event
153
+ throw :pass
154
+ end
155
+ end
156
+
157
+ ##
158
+ # Registers a callback for when this call is unjoined from another call or a mixer
159
+ #
160
+ # @param [Call, String, Hash, nil] target the target to guard on. May be a Call object, a call ID (String, Hash) or a mixer name (Hash)
161
+ # @option target [String] call_id The call ID to guard on
162
+ # @option target [String] mixer_name The mixer name to guard on
163
+ #
164
+ def on_unjoined(target = nil, &block)
165
+ register_event_handler Punchblock::Event::Unjoined, *guards_for_target(target) do |event|
166
+ block.call event
167
+ throw :pass
168
+ end
169
+ end
170
+
171
+ # @private
172
+ def guards_for_target(target)
173
+ target ? [join_options_with_target(target)] : []
174
+ end
175
+
143
176
  def on_end(&block)
144
177
  register_event_handler Punchblock::Event::End do |event|
145
178
  block.call event
@@ -233,11 +266,11 @@ module Adhearsion
233
266
  end
234
267
 
235
268
  def mute
236
- write_and_await_response ::Punchblock::Command::Mute.new
269
+ write_and_await_response Punchblock::Command::Mute.new
237
270
  end
238
271
 
239
272
  def unmute
240
- write_and_await_response ::Punchblock::Command::Unmute.new
273
+ write_and_await_response Punchblock::Command::Unmute.new
241
274
  end
242
275
 
243
276
  # @private
@@ -287,8 +320,10 @@ module Adhearsion
287
320
  "#<#{self.class}:#{id} #{attrs.join ', '}>"
288
321
  end
289
322
 
290
- def execute_controller(controller, completion_callback = nil)
323
+ def execute_controller(controller = nil, completion_callback = nil, &block)
324
+ raise ArgumentError if controller && block_given?
291
325
  call = current_actor
326
+ controller ||= CallController.new call, &block
292
327
  Thread.new do
293
328
  catching_standard_errors do
294
329
  begin
@@ -139,8 +139,9 @@ module Adhearsion
139
139
  #
140
140
  def hangup(headers = nil)
141
141
  block_until_resumed
142
- hangup_response = call.hangup headers
143
- after_call unless hangup_response == false
142
+ call.hangup headers
143
+ after_call
144
+ raise Call::Hangup
144
145
  end
145
146
 
146
147
  # @private
@@ -221,6 +222,8 @@ module Adhearsion
221
222
  end
222
223
  end
223
224
 
225
+ alias :safely :catching_standard_errors
226
+
224
227
  # @private
225
228
  def block_until_resumed
226
229
  instance_variable_defined?(:@pause_latch) && @pause_latch.wait