|
| 1 | +## Documentation |
| 2 | + |
| 3 | +- A function with an extensions has a shutdown timeout of 2s. |
| 4 | +- 2,000 ms – A function with one or more registered external extensions |
| 5 | + |
| 6 | +#### CloudWatch Metrics |
| 7 | + |
| 8 | +When using Extensions, your function's CloudWatch `Duration` metrics will be the sum of your response time combined with your extension's execution time. For example, if your request takes `200ms` to respond but your need to process a background task which takes `1000ms` your duration will be `1200ms` total. For more details see the "Performance impact and extension overhead" section of the [Lambda Extensions API |
| 9 | +](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-extensions-api.html) |
| 10 | + |
| 11 | +Thankfully, when using Lambda Extensions, CloudWatch will create a `PostRuntimeExtensionsDuration` metric that you can use to isolate your true response times `Duration` [using some metric math](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/using-metric-math.html). Here is an example |
| 12 | + |
| 13 | +#### Logging |
| 14 | + |
| 15 | +Default :fatal. |
| 16 | + |
| 17 | +```yaml |
| 18 | +Environment: |
| 19 | + Variables: |
| 20 | + LAMBDA_PUNCH_LOG_LEVEL: debug |
| 21 | +``` |
| 22 | +
|
| 23 | +## Development |
| 24 | +
|
| 25 | +```ruby |
| 26 | + |
| 27 | +# Queuing Works |
| 28 | +class Queue |
| 29 | + JOBS = [] |
| 30 | + def self.push(block) |
| 31 | + JOBS << block |
| 32 | + end |
| 33 | +end |
| 34 | +module LambdaPunch |
| 35 | + def push(&block) |
| 36 | + Queue.push(block) |
| 37 | + end |
| 38 | + extend self |
| 39 | +end |
| 40 | +LambdaPunch.push do |
| 41 | + sleep(1) |
| 42 | +end |
| 43 | + |
| 44 | +# Seconds to Milliseconds |
| 45 | +(1000.0 * 0.1).to_i # => 100 |
| 46 | +(1000.0 * 0.01).to_i # => 10 |
| 47 | + |
| 48 | +# Milliseconds to Seconds |
| 49 | +100 / 1000.0 # => 0.1 |
| 50 | +10 / 1000.0 # => 0.01 |
| 51 | + |
| 52 | +>> Timeout.timeout(0.01) { sleep(0.009) } |
| 53 | +>> Timeout.timeout(0.01) { sleep(0.011) } |
| 54 | +Timeout::Error (execution expired) |
| 55 | + |
| 56 | + |
| 57 | +# Do concurrent ruby timeouts do anything when not needed? No! |
| 58 | +require 'concurrent' |
| 59 | +require 'concurrent/edge/cancellation' |
| 60 | +t = Concurrent::Cancellation.timeout(10) |
| 61 | +Concurrent.global_io_executor.post(t) do |timeout| |
| 62 | + puts 'here' |
| 63 | +end |
| 64 | + |
| 65 | + |
| 66 | +# Final working solution I like. |
| 67 | +require 'rb-inotify' |
| 68 | +require 'concurrent' |
| 69 | +require 'concurrent/edge/cancellation' |
| 70 | +@file = "./lambdapunch-handled" |
| 71 | +File.open(@file, 'w') { |f| f.write('') } |
| 72 | +def noop ; true ; end |
| 73 | +def noopn(n) ; sleep(n) ; end |
| 74 | +@request_id = nil |
| 75 | +@notifier = INotify::Notifier.new |
| 76 | +@notifier.watch(@file, :modify, :oneshot) { File.read(@file) } |
| 77 | +def p1 |
| 78 | + @c1, @o1 = Concurrent::Cancellation.new |
| 79 | + @p1 = Concurrent::Promises.future do |
| 80 | + @notifier.process |
| 81 | + puts 'notified' |
| 82 | + File.read(@file) |
| 83 | + end |
| 84 | +end |
| 85 | +def p2 |
| 86 | + @c2 = Concurrent::Cancellation.timeout(60) |
| 87 | + @p2 = Concurrent::Promises.future do |
| 88 | + noop until @c1.canceled? || @c2.canceled? |
| 89 | + if @c2.canceled? |
| 90 | + puts 'timeout' |
| 91 | + @c1.origin.resolve |
| 92 | + :timeout |
| 93 | + else |
| 94 | + puts 'notifier_resolved' |
| 95 | + :notifier_resolved |
| 96 | + end |
| 97 | + end |
| 98 | +end |
| 99 | +@p3 = Concurrent::Promises.any_resolved_future(p2, p1) |
| 100 | +File.open(@file,'w') { |f| f.write('123') } |
| 101 | +@p3.wait.value |
| 102 | +@notifier.close |
| 103 | + |
| 104 | + |
| 105 | + |
| 106 | + |
| 107 | + |
| 108 | +p1 = Concurrent::Promises.future do |
| 109 | + do_stuff until c.canceled? |
| 110 | + |
| 111 | +end |
| 112 | +# => # |
| 113 | + |
| 114 | +c.origin.resolve |
| 115 | +# => # |
| 116 | +async_task.value! |
| 117 | + |
| 118 | + |
| 119 | + |
| 120 | + |
| 121 | + |
| 122 | + |
| 123 | + |
| 124 | + |
| 125 | +@i = 1 |
| 126 | +def do_stuff ; @i += 1 ; end |
| 127 | +c, o = Concurrent::Cancellation.new |
| 128 | +Concurrent::Promises.future(c) do |c| |
| 129 | + # Do work repeatedly until it is cancelled |
| 130 | + do_stuff until c.canceled? |
| 131 | + :stopped_gracefully |
| 132 | +end |
| 133 | +o.resolve |
| 134 | + |
| 135 | +require 'concurrent' |
| 136 | +require 'concurrent/edge/cancellation' |
| 137 | +@i = 1 |
| 138 | +def do_stuff ; @i += 1 ; end |
| 139 | +# t = Concurrent::Cancellation.new Concurrent::Promises.schedule(0.02) |
| 140 | +t = Concurrent::Cancellation.timeout 59.99802703818213 |
| 141 | +p = Concurrent.global_io_executor.post(t) do |t| |
| 142 | + do_stuff until t.canceled? |
| 143 | + puts('here') |
| 144 | + :done |
| 145 | +end |
| 146 | +t.origin.resolve |
| 147 | +t.origin.resolved? |
| 148 | +t.origin.wait |
| 149 | +puts 'here' |
| 150 | + |
| 151 | +c, o = Concurrent::Cancellation.new |
| 152 | +p = Concurrent::Promises.future(c) do |c| |
| 153 | + true until c.canceled? |
| 154 | +end |
| 155 | + |
| 156 | + |
| 157 | + |
| 158 | + |
| 159 | +p1 = Concurrent::Promises.future(3) do |n| |
| 160 | + sleep(n) |
| 161 | + puts "notifier#{n}" |
| 162 | + n |
| 163 | +end |
| 164 | +p2 = Concurrent::Promises.future(5) do |n| |
| 165 | + sleep(n) |
| 166 | + puts "notifier#{n}" |
| 167 | + n |
| 168 | +end |
| 169 | +Concurrent::Promises.any_resolved_future(p1,p2).wait.value |
| 170 | + |
| 171 | + |
| 172 | + |
| 173 | + |
| 174 | +require 'concurrent' |
| 175 | +require 'concurrent/edge/cancellation' |
| 176 | +@i = 1 |
| 177 | +def do_stuff ; @i += 1 ; end |
| 178 | +p1 = Concurrent::Promises.future(3) do |n| |
| 179 | + sleep(n) |
| 180 | + puts "notifier#{n}" |
| 181 | + n |
| 182 | +end |
| 183 | +p2 = Concurrent::Promises.future(5) do |n| |
| 184 | + sleep(n) |
| 185 | + puts "notifier#{n}" |
| 186 | + n |
| 187 | +end |
| 188 | +Concurrent::Promises.any_resolved_future(p1,p2).wait.value |
| 189 | + |
| 190 | +c = Concurrent::Cancellation.timeout(12.882) |
| 191 | +p2 = Concurrent::Promises.future(c) do |c| |
| 192 | + do_stuff until c.canceled? |
| 193 | + puts 'timeout' |
| 194 | + :request_id_payload |
| 195 | +end |
| 196 | +Concurrent::Promises.any_resolved_future(p1,p2).wait |
| 197 | + |
| 198 | + |
| 199 | +@i = 1 |
| 200 | +@rid = 'aaa-bbb-ccc' |
| 201 | +def do_stuff ; @i += 1 ; if @i == 588928 ; @invoked = true ; end ; end |
| 202 | +t = Concurrent::Cancellation.timeout 5 |
| 203 | +p = Concurrent::Promises.future do |
| 204 | + do_stuff until @invoked || t.canceled? |
| 205 | + @rid |
| 206 | +end |
| 207 | +p.wait |
| 208 | + |
| 209 | +@invoked = false |
| 210 | + |
| 211 | +!t.canceled? |
| 212 | + |
| 213 | +p = Concurrent::Promises.future(t) { |t| } |
| 214 | +while |
| 215 | + puts 't' |
| 216 | +end |
| 217 | +t.cancel |
| 218 | +puts 'here' |
| 219 | + |
| 220 | +p.set('test') |
| 221 | + |
| 222 | +p.fulfill('test') |
| 223 | +t.origin.wait |
| 224 | + |
| 225 | + |
| 226 | +t.origin.wait |
| 227 | +puts 'here' |
| 228 | + |
| 229 | + |
| 230 | + |
| 231 | + |
| 232 | + |
| 233 | + |
| 234 | +Time.at(1624399969622/1000.0) |
| 235 | +=> 2021-06-22 18:12:49 2608857/4194304 -0400 |
| 236 | + |
| 237 | +(Time.at(1624399969622/1000.0).to_f * 1000.0).to_i |
| 238 | +=> 1624399969622 |
| 239 | + |
| 240 | +>> Time.at(1624399969622/1000.0) < Time.at(1624399969623/1000.0) |
| 241 | +=> true |
| 242 | +>> Time.at(1624399969622/1000.0) < Time.at(1624399969621/1000.0) |
| 243 | +=> false |
| 244 | +``` |
| 245 | + |
| 246 | +## Benchmarks |
| 247 | + |
| 248 | +ab -n 100 -c 1 ... |
| 249 | + |
| 250 | +Time taken for tests: 12.814 seconds |
| 251 | + |
| 252 | +50% 124 |
| 253 | +66% 127 |
| 254 | +75% 128 |
| 255 | +80% 129 |
| 256 | +90% 143 |
| 257 | +95% 175 |
| 258 | +98% 202 |
| 259 | +99% 212 |
| 260 | +100% 212 (longest request) |
| 261 | + |
| 262 | +Time taken for tests: 12.998 seconds |
| 263 | + |
| 264 | +50% 125 |
| 265 | +66% 130 |
| 266 | +75% 133 |
| 267 | +80% 135 |
| 268 | +90% 146 |
| 269 | +95% 176 |
| 270 | +98% 200 |
| 271 | +99% 206 |
| 272 | +100% 206 (longest request) |
0 commit comments