Skip to content

Commit 7edb2dd

Browse files
authored
Merge pull request #30 from bakdata/feature/r-native-runtime
Feature/r native runtime
2 parents 6f18dbb + 6589532 commit 7edb2dd

8 files changed

Lines changed: 230 additions & 72 deletions

File tree

Pipfile.lock

Lines changed: 31 additions & 31 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

runtime/src/bootstrap

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,4 @@
22

33
set -euo pipefail
44

5-
# Processing
6-
while true
7-
do
8-
HEADERS="$(mktemp)"
9-
# Get an event
10-
EVENT_DATA=$(curl -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")
11-
REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)
12-
13-
/opt/R/bin/Rscript /opt/runtime.R "$EVENT_DATA" ${REQUEST_ID}
14-
done
5+
/opt/R/bin/Rscript /opt/bootstrap.R

runtime/src/bootstrap.R

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
source('/opt/runtime.R')
2+
tryCatch({
3+
function_name <- initializeRuntime()
4+
while (TRUE) {
5+
handle_request(function_name)
6+
rm(list=ls())
7+
source('/opt/runtime.R')
8+
function_name <- initializeRuntime()
9+
}
10+
}, error = throwInitError)

runtime/src/runtime.R

Lines changed: 79 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,83 @@
1-
library(httr)
2-
library(jsonlite)
3-
library(logging)
4-
51
to_str <- function(x) {
6-
return(paste(capture.output(print(x)), collapse = "\n"))
2+
return(paste(capture.output(print(x)), collapse = "\n"))
3+
}
4+
5+
error_to_payload <- function(error) {
6+
return(list(errorMessage = toString(error), errorType = class(error)[1]))
7+
}
8+
9+
post_error <- function(error, url) {
10+
logerror(error)
11+
res <- POST(url,
12+
add_headers("Lambda-Runtime-Function-Error-Type" = "Unhandled"),
13+
body = error_to_payload(error),
14+
encode = "json")
15+
loginfo("Posted result:\n%s", to_str(res))
16+
}
17+
18+
get_source_file_name <- function(file_base_name) {
19+
file_name <- paste0(file_base_name, ".R")
20+
if (! file.exists(file_name)) {
21+
file_name <- paste0(file_base_name, ".r")
22+
}
23+
if (! file.exists(file_name)) {
24+
stop(paste0('Source file does not exist: ', file_base_name, '.[R|r]'))
25+
}
26+
return(file_name)
27+
}
28+
29+
invoke_lambda <- function(EVENT_DATA, function_name) {
30+
params <- fromJSON(EVENT_DATA)
31+
loginfo("Invoking function '%s' with parameters:\n%s", function_name, to_str(params))
32+
result <- do.call(function_name, params)
33+
loginfo("Function returned:\n%s", to_str(result))
34+
return(result)
35+
}
36+
37+
initializeRuntime <- function() {
38+
library(httr)
39+
library(jsonlite)
40+
library(logging)
41+
42+
HANDLER <- Sys.getenv("_HANDLER")
43+
HANDLER_split <- strsplit(HANDLER, ".", fixed = TRUE)[[1]]
44+
file_base_name <- HANDLER_split[1]
45+
file_name <- get_source_file_name(file_base_name)
46+
loginfo("Sourcing '%s'", file_name)
47+
source(file_name)
48+
return(HANDLER_split[2])
749
}
850

9-
HANDLER <- Sys.getenv("_HANDLER")
1051
AWS_LAMBDA_RUNTIME_API <- Sys.getenv("AWS_LAMBDA_RUNTIME_API")
11-
args = commandArgs(trailingOnly = TRUE)
12-
EVENT_DATA <- args[1]
13-
REQUEST_ID <- args[2]
14-
15-
HANDLER_split <- strsplit(HANDLER, ".", fixed = TRUE)[[1]]
16-
file_name <- paste0(HANDLER_split[1], ".R")
17-
function_name <- HANDLER_split[2]
18-
loginfo("Sourcing '%s'", file_name)
19-
source(file_name)
20-
params <- fromJSON(EVENT_DATA)
21-
loginfo("Invoking function '%s' with parameters: %s", function_name, to_str(params))
22-
result <- do.call(function_name, params)
23-
loginfo("Function returned: %s", to_str(result))
24-
url <- paste0("http://",
25-
AWS_LAMBDA_RUNTIME_API,
26-
"/2018-06-01/runtime/invocation/",
27-
REQUEST_ID,
28-
"/response")
29-
res <- POST(url, body = list(result = result), encode = "json")
30-
loginfo("Posted result: %s", to_str(res))
52+
API_ENDPOINT <- paste0("http://", AWS_LAMBDA_RUNTIME_API, "/2018-06-01/runtime/")
53+
54+
throwInitError <- function(error) {
55+
url <- paste0(API_ENDPOINT, "init/error")
56+
post_error(error, url)
57+
stop()
58+
}
59+
60+
throwRuntimeError <- function(error, REQUEST_ID) {
61+
url <- paste0(API_ENDPOINT, "invocation/", REQUEST_ID, "/error")
62+
post_error(error, url)
63+
}
64+
65+
postResult <- function(result, REQUEST_ID) {
66+
url <- paste0(API_ENDPOINT, "invocation/", REQUEST_ID, "/response")
67+
res <- POST(url, body = list(result = result), encode = "json")
68+
loginfo("Posted result:\n%s", to_str(res))
69+
}
70+
71+
handle_request <- function(function_name) {
72+
event_url <- paste0(API_ENDPOINT, "invocation/next")
73+
event_response <- GET(event_url)
74+
REQUEST_ID <- event_response$headers$`Lambda-Runtime-Aws-Request-Id`
75+
tryCatch({
76+
EVENT_DATA <- rawToChar(event_response$content)
77+
result <- invoke_lambda(EVENT_DATA, function_name)
78+
postResult(result, REQUEST_ID)
79+
},
80+
error = function(error) {
81+
throwRuntimeError(error, REQUEST_ID)
82+
})
83+
}

template.yaml

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,54 @@ Resources:
1313
Type: 'AWS::Serverless::Function'
1414
Properties:
1515
Handler: script.handler
16+
LowerCaseExtensionFunction:
17+
Type: 'AWS::Serverless::Function'
18+
Properties:
19+
Handler: lowercase.handler
20+
MissingFunctionFunction:
21+
Type: 'AWS::Serverless::Function'
22+
Properties:
23+
Handler: script.handler_missing
24+
MissingSourceFileFunction:
25+
Type: 'AWS::Serverless::Function'
26+
Properties:
27+
Handler: missing.handler
28+
MultipleArgumentsFunction:
29+
Type: 'AWS::Serverless::Function'
30+
Properties:
31+
Handler: script.handler_with_multiple_arguments
32+
VariableArgumentsFunction:
33+
Type: 'AWS::Serverless::Function'
34+
Properties:
35+
Handler: script.handler_with_variable_arguments
1636
MatrixFunction:
1737
Type: 'AWS::Serverless::Function'
1838
Properties:
1939
Handler: matrix.handler
2040
Layers:
21-
- !Ref RuntimeLayer
2241
- !Ref RecommendedLayer
42+
MissingLibraryFunction:
43+
Type: 'AWS::Serverless::Function'
44+
Properties:
45+
Handler: matrix.handler
2346
AWSFunction:
2447
Type: 'AWS::Serverless::Function'
2548
Properties:
2649
Handler: aws.handler
2750
Layers:
28-
- !Ref RuntimeLayer
2951
- !Ref AWSLayer
3052
RuntimeLayer:
3153
Type: AWS::Serverless::LayerVersion
3254
Properties:
33-
LayerName: runtime
55+
LayerName: r-runtime
3456
ContentUri: runtime/build/layer/
3557
RecommendedLayer:
3658
Type: AWS::Serverless::LayerVersion
3759
Properties:
38-
LayerName: runtime
60+
LayerName: r-recommended
3961
ContentUri: recommended/build/layer/
4062
AWSLayer:
4163
Type: AWS::Serverless::LayerVersion
4264
Properties:
43-
LayerName: runtime
65+
LayerName: r-awspack
4466
ContentUri: awspack/build/layer/

tests/R/lowercase.r

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
handler <- function(x) {
2+
return(x + 1)
3+
}

tests/R/script.R

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
11
handler <- function(x) {
22
return(x + 1)
33
}
4+
5+
handler_with_multiple_arguments <- function(x, y) {
6+
return(list(x = x, y = y))
7+
}
8+
9+
handler_with_variable_arguments <- function(...) {
10+
return(1)
11+
}

0 commit comments

Comments
 (0)