Skip to content

Commit 4a352cb

Browse files
committed
Add ActiveJob & Tests!
1 parent ccbe358 commit 4a352cb

27 files changed

+509
-38
lines changed

Diff for: .gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/.bundle/*
2-
/vendor/bunlde/*
2+
/vendor/bundle
33
/.yardoc
44
/_yardoc/
55
/coverage/

Diff for: Gemfile.lock

+132
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,142 @@ PATH
1010
GEM
1111
remote: https://rubygems.org/
1212
specs:
13+
actioncable (6.1.4)
14+
actionpack (= 6.1.4)
15+
activesupport (= 6.1.4)
16+
nio4r (~> 2.0)
17+
websocket-driver (>= 0.6.1)
18+
actionmailbox (6.1.4)
19+
actionpack (= 6.1.4)
20+
activejob (= 6.1.4)
21+
activerecord (= 6.1.4)
22+
activestorage (= 6.1.4)
23+
activesupport (= 6.1.4)
24+
mail (>= 2.7.1)
25+
actionmailer (6.1.4)
26+
actionpack (= 6.1.4)
27+
actionview (= 6.1.4)
28+
activejob (= 6.1.4)
29+
activesupport (= 6.1.4)
30+
mail (~> 2.5, >= 2.5.4)
31+
rails-dom-testing (~> 2.0)
32+
actionpack (6.1.4)
33+
actionview (= 6.1.4)
34+
activesupport (= 6.1.4)
35+
rack (~> 2.0, >= 2.0.9)
36+
rack-test (>= 0.6.3)
37+
rails-dom-testing (~> 2.0)
38+
rails-html-sanitizer (~> 1.0, >= 1.2.0)
39+
actiontext (6.1.4)
40+
actionpack (= 6.1.4)
41+
activerecord (= 6.1.4)
42+
activestorage (= 6.1.4)
43+
activesupport (= 6.1.4)
44+
nokogiri (>= 1.8.5)
45+
actionview (6.1.4)
46+
activesupport (= 6.1.4)
47+
builder (~> 3.1)
48+
erubi (~> 1.4)
49+
rails-dom-testing (~> 2.0)
50+
rails-html-sanitizer (~> 1.1, >= 1.2.0)
51+
activejob (6.1.4)
52+
activesupport (= 6.1.4)
53+
globalid (>= 0.3.6)
54+
activemodel (6.1.4)
55+
activesupport (= 6.1.4)
56+
activerecord (6.1.4)
57+
activemodel (= 6.1.4)
58+
activesupport (= 6.1.4)
59+
activestorage (6.1.4)
60+
actionpack (= 6.1.4)
61+
activejob (= 6.1.4)
62+
activerecord (= 6.1.4)
63+
activesupport (= 6.1.4)
64+
marcel (~> 1.0.0)
65+
mini_mime (>= 1.1.0)
66+
activesupport (6.1.4)
67+
concurrent-ruby (~> 1.0, >= 1.0.2)
68+
i18n (>= 1.6, < 2)
69+
minitest (>= 5.1)
70+
tzinfo (~> 2.0)
71+
zeitwerk (~> 2.3)
72+
builder (3.2.4)
73+
coderay (1.1.3)
1374
concurrent-ruby (1.1.9)
75+
crass (1.0.6)
76+
erubi (1.10.0)
1477
ffi (1.15.3)
78+
globalid (0.4.2)
79+
activesupport (>= 4.2.0)
80+
i18n (1.8.10)
81+
concurrent-ruby (~> 1.0)
82+
loofah (2.10.0)
83+
crass (~> 1.0.2)
84+
nokogiri (>= 1.5.9)
85+
mail (2.7.1)
86+
mini_mime (>= 0.1.1)
87+
marcel (1.0.1)
88+
method_source (1.0.0)
89+
mini_mime (1.1.0)
1590
minitest (5.14.4)
91+
minitest-focus (1.3.1)
92+
minitest (>= 4, < 6)
93+
nio4r (2.5.7)
94+
nokogiri (1.11.7-x86_64-darwin)
95+
racc (~> 1.4)
96+
nokogiri (1.11.7-x86_64-linux)
97+
racc (~> 1.4)
98+
pry (0.14.1)
99+
coderay (~> 1.1)
100+
method_source (~> 1.0)
101+
racc (1.5.2)
102+
rack (2.2.3)
103+
rack-test (1.1.0)
104+
rack (>= 1.0, < 3)
105+
rails (6.1.4)
106+
actioncable (= 6.1.4)
107+
actionmailbox (= 6.1.4)
108+
actionmailer (= 6.1.4)
109+
actionpack (= 6.1.4)
110+
actiontext (= 6.1.4)
111+
actionview (= 6.1.4)
112+
activejob (= 6.1.4)
113+
activemodel (= 6.1.4)
114+
activerecord (= 6.1.4)
115+
activestorage (= 6.1.4)
116+
activesupport (= 6.1.4)
117+
bundler (>= 1.15.0)
118+
railties (= 6.1.4)
119+
sprockets-rails (>= 2.0.0)
120+
rails-dom-testing (2.0.3)
121+
activesupport (>= 4.2.0)
122+
nokogiri (>= 1.6)
123+
rails-html-sanitizer (1.3.0)
124+
loofah (~> 2.3)
125+
railties (6.1.4)
126+
actionpack (= 6.1.4)
127+
activesupport (= 6.1.4)
128+
method_source
129+
rake (>= 0.13)
130+
thor (~> 1.0)
16131
rake (13.0.3)
17132
rb-inotify (0.10.1)
18133
ffi (~> 1.0)
134+
sprockets (4.0.2)
135+
concurrent-ruby (~> 1.0)
136+
rack (> 1, < 3)
137+
sprockets-rails (3.2.2)
138+
actionpack (>= 4.0)
139+
activesupport (>= 4.0)
140+
sprockets (>= 3.0.0)
141+
thor (1.1.0)
19142
timeout (0.1.1)
143+
tzinfo (2.0.4)
144+
concurrent-ruby (~> 1.0)
145+
websocket-driver (0.7.5)
146+
websocket-extensions (>= 0.1.0)
147+
websocket-extensions (0.1.5)
148+
zeitwerk (2.4.2)
20149

21150
PLATFORMS
22151
x86_64-darwin-19
@@ -25,6 +154,9 @@ PLATFORMS
25154
DEPENDENCIES
26155
lambda_punch!
27156
minitest
157+
minitest-focus
158+
pry
159+
rails
28160
rake
29161

30162
BUNDLED WITH

Diff for: README.md

+14-2
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,19 @@ end
7272

7373
### ActiveJob
7474

75-
🚧 COMING SOON 🚧 - A simple ActiveJob adapter...
75+
You can use LambdaPunch with Rails' ActiveJob. **For a more robust background job solution, please consider using AWS SQS with the [Lambdakiq](https://github.com/customink/lambdakiq) gem.**
76+
77+
```ruby
78+
config.active_job.queue_adapter = :lambda_punch
79+
```
7680

7781
### Timeouts
7882

7983
Your function's timeout is the max amount to handle the request and process all extension's invoke events. If your function times out, it is possible that queued jobs will not be processed until the next invoke.
8084

8185
If your application integrates with API Gateway (which has a 30 second timeout) then it is possible your function can be set with a higher timeout to perform background work. Since work is done after each invoke, the LambdaPunch queue should be empty when your function receives the `SHUTDOWN` event. If jobs are in the queue when this happens they will have two seconds max to work them down before being lost.
8286

83-
**For a more robust background job solution, please consider using AWS SQS with the [Lambdakiq](https://github.com/customink/lambdakiq) gem. A drop-in replacement for [Sidekiq](https://github.com/mperham/sidekiq) when running Rails in AWS Lambda using the [Lamby](https://lamby.custominktech.com/) gem.**
87+
**For a more robust background job solution, please consider using AWS SQS with the [Lambdakiq](https://github.com/customink/lambdakiq) gem.**
8488

8589
### Logging
8690

@@ -92,6 +96,14 @@ Environment:
9296
LAMBDA_PUNCH_LOG_LEVEL: debug
9397
```
9498
99+
### Errors
100+
101+
As jobs are worked off the queue, all job errors are simply logged. If you want to customize this, you can set your own error handler.
102+
103+
```ruby
104+
LambdaPunch.error_handler = lambda { |e| ... }
105+
```
106+
95107
## 📊 CloudWatch Metrics
96108

97109
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 background task takes `1000ms` your duration will be a combined `1200ms`. For more details see the _"Performance impact and extension overhead"_ section of the [Lambda Extensions API

Diff for: Rakefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ require "rake/testtask"
44
Rake::TestTask.new(:test) do |t|
55
t.libs << "test"
66
t.libs << "lib"
7-
t.test_files = FileList["test/**/*_test.rb"]
7+
t.test_files = FileList["test/cases/*_test.rb"]
88
end
99

1010
task default: :test

Diff for: lambda_punch.gemspec

+3
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,7 @@ Gem::Specification.new do |spec|
2222
spec.add_dependency "rake"
2323
spec.add_dependency "rb-inotify"
2424
spec.add_dependency "timeout"
25+
spec.add_development_dependency "minitest-focus"
26+
spec.add_development_dependency "pry"
27+
spec.add_development_dependency "rails"
2528
end

Diff for: lib/lambda_punch.rb

+14-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
require 'lambda_punch/worker'
1414
require 'lambda_punch/version'
1515
require 'lambda_punch/notifier'
16-
require 'lambda_punch/railtie' if defined?(Rails)
16+
if defined?(Rails)
17+
require 'lambda_punch/railtie'
18+
require 'lambda_punch/rails/active_job'
19+
end
1720

1821
module LambdaPunch
1922

@@ -45,6 +48,16 @@ def handled!(context)
4548
Notifier.handled!(context)
4649
end
4750

51+
def error_handler
52+
@error_handler ||= lambda do |e|
53+
logger.error "Queue#call::error => #{e.message}"
54+
end
55+
end
56+
57+
def error_handler=(func)
58+
@error_handler = func
59+
end
60+
4861
extend self
4962

5063
end

Diff for: lib/lambda_punch/logger.rb

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
module LambdaPunch
22
class Logger
33

4+
attr_reader :level
5+
6+
def initialize
7+
@level = (ENV['LAMBDA_PUNCH_LOG_LEVEL'] || 'error').upcase.to_sym
8+
end
9+
410
def logger
511
@logger ||= ::Logger.new(STDOUT).tap do |l|
6-
l.level = level
12+
l.level = logger_level
713
l.formatter = proc { |_s, _d, _p, m| "[LambdaPunch] #{m}\n" }
814
end
915
end
1016

11-
def level=(value)
12-
@level = value.to_s
13-
@logger = nil
14-
end
15-
1617
private
1718

18-
def level
19-
l = (@level || ENV['LAMBDA_PUNCH_LOG_LEVEL'] || 'error').upcase.to_sym
20-
::Logger.const_defined?(l) ? ::Logger.const_get(l) : ::Logger::ERROR
19+
def logger_level
20+
::Logger.const_defined?(@level) ? ::Logger.const_get(@level) : ::Logger::ERROR
2121
end
2222

2323
end

Diff for: lib/lambda_punch/queue.rb

+1-6
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ def call
1818
begin
1919
job.call
2020
rescue => e
21-
logger.error "Queue#call::error => #{e.message}"
22-
# ...
21+
LambdaPunch.error_handler.call(e)
2322
end
2423
end
2524
true
@@ -33,9 +32,5 @@ def jobs
3332
self.class.jobs
3433
end
3534

36-
def logger
37-
LambdaPunch.logger
38-
end
39-
4035
end
4136
end

Diff for: lib/lambda_punch/rails/active_job.rb

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module ActiveJob
2+
module QueueAdapters
3+
class LambdaPunchAdapter
4+
5+
def enqueue(job, options = {})
6+
job_data = job.serialize
7+
LambdaPunch.push { ActiveJob::Base.execute(job_data) }
8+
end
9+
10+
def enqueue_at(job, timestamp)
11+
enqueue(job)
12+
end
13+
14+
end
15+
end
16+
end

Diff for: lib/lambda_punch/railtie.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require 'rails'
22
require 'rails/engine'
3-
3+
require 'active_job'
4+
45
module LambdaPunch
56
class Railtie < Rails::Railtie
67
railtie_name :lambda_punch

Diff for: lib/lambda_punch/worker.rb

+23-6
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def initialize(event_payload)
6464
# also ensures any clean up is done. For example, closing file notifications.
6565
#
6666
def call
67-
Timeout.timeout(timeout) { @notifier.process unless invoked? }
67+
timeout { @notifier.process unless invoked? }
6868
rescue Timeout::Error
6969
logger.error "Worker#call => Function timeout reached."
7070
ensure
@@ -76,10 +76,11 @@ def call
7676

7777
# The Notifier's watch handler would set this instance variable to `true`. We also return `true`
7878
# if the extension's invoke palyload event has a `requestId` matching what the handler has written
79-
# to the `LambdaPunch::Notifier` file location. See also `request_ids_match?` method.
79+
# to the `LambdaPunch::Notifier` file location. See also `request_ids_match?` method. Lastly if
80+
# the timeout
8081
#
8182
def invoked?
82-
@invoked || request_ids_match?
83+
@invoked || request_ids_match? || timed_out?
8384
end
8485

8586
# The unique AWS reqeust id that both the extension and handler receive for each invoke. This one
@@ -102,13 +103,29 @@ def request_ids_match?
102103
request_id_payload == (request_id_notifier || Notifier.request_id)
103104
end
104105

105-
# The function's timeout in seconds using the `INVOKE` event payload's `deadlineMs` value.
106+
# A safe timeout method which accounts for a 0 or negative timeout value.
106107
#
107108
def timeout
109+
@timeout = timeout_seconds
110+
if timed_out?
111+
yield
112+
else
113+
Timeout.timeout(@timeout) { yield }
114+
end
115+
end
116+
117+
# Helps guard for deadline milliseconds in the past.
118+
#
119+
def timed_out?
120+
@timeout == 0 || @timeout < 0
121+
end
122+
123+
# The function's timeout in seconds using the `INVOKE` event payload's `deadlineMs` value.
124+
#
125+
def timeout_seconds
108126
deadline_milliseconds = @event_payload['deadlineMs']
109127
deadline = Time.at(deadline_milliseconds / 1000.0)
110-
deadline_timeout = deadline - Time.now
111-
deadline_timeout > 0 ? deadline_timeout : 0
128+
deadline - Time.now
112129
end
113130

114131
# Our `LambdaPunch::Notifier` instance callback.

0 commit comments

Comments
 (0)