diff --git a/.gitignore b/.gitignore index b9c46f2..15c9bcf 100755 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ lambda-*.tar *.lock *.swp + +.DS_Store diff --git a/elixir_runtime/config/config.exs b/elixir_runtime/config/config.exs index a845745..a0f0d89 100644 --- a/elixir_runtime/config/config.exs +++ b/elixir_runtime/config/config.exs @@ -1,7 +1,7 @@ # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 -use Mix.Config +import Config config :logger, :console, @@ -9,4 +9,4 @@ config :logger, metadata: [:module, :function, :line] # Uncomment to enable environment-specific configuration -# import_config "#{Mix.env()}.exs" +# import_config "#{config_env()}.exs" diff --git a/elixir_runtime/lib/elixir_runtime/lambda_service_client.ex b/elixir_runtime/lib/elixir_runtime/lambda_service_client.ex index 1e6b9c3..5037e53 100644 --- a/elixir_runtime/lib/elixir_runtime/lambda_service_client.ex +++ b/elixir_runtime/lib/elixir_runtime/lambda_service_client.ex @@ -23,17 +23,17 @@ defmodule ElixirRuntime.LambdaServiceClient do ) :: no_return def invocation_error(err_msg, id) do url = - 'http://#{service_endpoint()}/2018-06-01/runtime/invocation/#{id}/error' + ~c"http://#{service_endpoint()}/2018-06-01/runtime/invocation/#{id}/error" - request = {url, [], 'text/plain', err_msg} + request = {url, [], ~c"text/plain", err_msg} {:ok, _response} = :httpc.request(:post, request, [], []) end @impl true @spec init_error(Monitor.Client.error()) :: no_return def init_error(err_msg) do - url = 'http://#{service_endpoint()}/2018-06-01/runtime/init/error' - request = {url, [], 'text/plain', err_msg} + url = ~c"http://#{service_endpoint()}/2018-06-01/runtime/init/error" + request = {url, [], ~c"text/plain", err_msg} {:ok, _response} = :httpc.request(:post, request, [], []) end @@ -44,16 +44,16 @@ defmodule ElixirRuntime.LambdaServiceClient do ) :: no_return def complete_invocation(id, response) do url = - 'http://#{service_endpoint()}/2018-06-01/runtime/invocation/#{id}/response' + ~c"http://#{service_endpoint()}/2018-06-01/runtime/invocation/#{id}/response" - request = {url, [], 'text/plain', response} + request = {url, [], ~c"text/plain", response} {:ok, _response} = :httpc.request(:post, request, [], []) end @impl true @spec next_invocation() :: Runtime.Client.invocation() def next_invocation do - url = 'http://#{service_endpoint()}/2018-06-01/runtime/invocation/next' + url = ~c"http://#{service_endpoint()}/2018-06-01/runtime/invocation/next" response = :httpc.request(:get, {url, []}, [], []) Logger.debug("Http get from #{url} was #{Kernel.inspect(response)}") parse(response) diff --git a/elixir_runtime/lib/elixir_runtime/loop.ex b/elixir_runtime/lib/elixir_runtime/loop.ex index 76ee035..87ffc49 100644 --- a/elixir_runtime/lib/elixir_runtime/loop.ex +++ b/elixir_runtime/lib/elixir_runtime/loop.ex @@ -50,8 +50,8 @@ defmodule ElixirRuntime.Loop do response = handler - |> Handler.invoke(Poison.decode!(body), context) - |> Poison.encode!() + |> Handler.invoke(Jason.decode!(body), context) + |> Jason.encode!() client.complete_invocation(id, response) end diff --git a/elixir_runtime/lib/elixir_runtime/monitor/error.ex b/elixir_runtime/lib/elixir_runtime/monitor/error.ex index b71089f..06d1c1f 100644 --- a/elixir_runtime/lib/elixir_runtime/monitor/error.ex +++ b/elixir_runtime/lib/elixir_runtime/monitor/error.ex @@ -9,7 +9,7 @@ defmodule ElixirRuntime.Monitor.Error do alias __MODULE__ - @derive Poison.Encoder + @derive Jason.Encoder defstruct [ :errorMessage, :errorType, @@ -23,15 +23,15 @@ defmodule ElixirRuntime.Monitor.Error do stackTrace: list } - @doc "Build an Error from a structured process-exit reason" - @spec from_exit_reason(error_type, {atom(), list()}) :: error + @doc "Build an Error from a process-exit reason" + @spec from_exit_reason(error_type, term) :: error + def from_exit_reason(error_type, reason) + def from_exit_reason(error_type, _reason = {error, stacktrace}) do exception = Exception.normalize(:error, error, stacktrace) build_error(error_name(error_type, exception), exception, stacktrace) end - @doc "Build an Error from an unknown process-exit reason" - @spec from_exit_reason(error_type, term) :: error def from_exit_reason(error_type, reason) do exception = Exception.normalize(:error, {"unexpected exit", reason}) build_error(error_name(error_type, exception), exception, []) diff --git a/elixir_runtime/lib/elixir_runtime/monitor/state.ex b/elixir_runtime/lib/elixir_runtime/monitor/state.ex index af135a7..a486e27 100644 --- a/elixir_runtime/lib/elixir_runtime/monitor/state.ex +++ b/elixir_runtime/lib/elixir_runtime/monitor/state.ex @@ -22,17 +22,17 @@ defmodule ElixirRuntime.Monitor.State do @doc "report an error before an invocation was started" @spec error(monitor_state, term()) :: no_return + def error(monitor_state, reason) + def error({:not_started, client}, reason) do Monitor.Error.from_exit_reason(:runtime, reason) - |> Poison.encode!() + |> Jason.encode!() |> client.init_error() end - @doc "report an error before an invocation was started" - @spec error(monitor_state, term()) :: no_return def error({:in_progress, id, client}, reason) do Monitor.Error.from_exit_reason(:function, reason) - |> Poison.encode!() + |> Jason.encode!() |> client.invocation_error(id) end diff --git a/elixir_runtime/lib/epmd/stub_client.ex b/elixir_runtime/lib/epmd/stub_client.ex index f4431ea..d1e9817 100644 --- a/elixir_runtime/lib/epmd/stub_client.ex +++ b/elixir_runtime/lib/epmd/stub_client.ex @@ -2,6 +2,8 @@ # SPDX-License-Identifier: MIT-0 defmodule EPMD.StubClient do + @doc since: "0.2.0" + @deprecated "No longer needed when using mix release." @moduledoc """ This module implements an EPMD client which does not actually coordinate with the real EPMD or allow the current node to communicate with other diff --git a/elixir_runtime/lib/mix/tasks/bootstrap.ex b/elixir_runtime/lib/mix/tasks/bootstrap.ex index 736589b..5e66f13 100644 --- a/elixir_runtime/lib/mix/tasks/bootstrap.ex +++ b/elixir_runtime/lib/mix/tasks/bootstrap.ex @@ -9,25 +9,23 @@ defmodule Mix.Tasks.Bootstrap do use Mix.Task - @runtime_libs "elixir_runtime-0.1.0/priv" - @shortdoc "Generate a bootstrap script for the project" def run(_) do - name = - Mix.Project.config() - |> Keyword.fetch!(:app) - |> to_string + name = app_name() path = "_build/#{Mix.env()}/rel/#{name}/bootstrap" Mix.Generator.create_file(path, bootstrap(name)) File.chmod!(path, 0o777) + + Mix.shell().info("Bootstrap file created: #{path}") + end # The bootstrap script contents defp bootstrap(app) when is_binary(app) do """ - \#!/bin/bash + \#!/bin/sh set -x @@ -38,11 +36,30 @@ defmodule Mix.Tasks.Bootstrap do export HOME \# So that distillery doesn't try to write any files + \# Ignored and not needed when using mix release export RELEASE_READ_ONLY=true - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$BASE/lib/#{@runtime_libs} + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$BASE/lib/#{runtime_app_name()}-#{runtime_version()}/priv - $EXE foreground + $EXE start """ end + + defp app_name() do + Mix.Project.config() + |> Keyword.fetch!(:app) + |> to_string + end + + defp runtime_app_name() do + Application.get_application(__MODULE__) + |> Atom.to_string() + + end + + defp runtime_version() do + Application.get_application(__MODULE__) + |> Application.spec(:vsn) + end + end diff --git a/elixir_runtime/lib/mix/tasks/gen_lambda_release.ex b/elixir_runtime/lib/mix/tasks/gen_lambda_release.ex index a11a911..4620710 100644 --- a/elixir_runtime/lib/mix/tasks/gen_lambda_release.ex +++ b/elixir_runtime/lib/mix/tasks/gen_lambda_release.ex @@ -2,6 +2,8 @@ # SPDX-License-Identifier: MIT-0 defmodule Mix.Tasks.GenLambdaRelease do + @doc since: "0.2.0" + @deprecated "No longer needed when using mix release." @moduledoc """ Generate a distillery release configuration file for lambda release builds. """ diff --git a/elixir_runtime/lib/mix/tasks/zip.ex b/elixir_runtime/lib/mix/tasks/zip.ex index b03e414..4f32253 100644 --- a/elixir_runtime/lib/mix/tasks/zip.ex +++ b/elixir_runtime/lib/mix/tasks/zip.ex @@ -7,10 +7,13 @@ defmodule Mix.Tasks.Zip do @shortdoc "zip the contents of the current release" def run(_) do path = release_path(app_name()) + zip_file = "#{app_name()}_lambda.zip" - cmd = "cd #{path} && zip -r lambda.zip * && cp lambda.zip #{System.cwd()}" + cmd = "set -xe && cd #{path} && zip -r #{zip_file} *" System.cmd("sh", ["-c", cmd]) + + Mix.shell().info("Zip file created: #{Path.join(path, zip_file)}") end defp app_name() do diff --git a/elixir_runtime/mix.exs b/elixir_runtime/mix.exs index 36ca8ef..cf05f12 100644 --- a/elixir_runtime/mix.exs +++ b/elixir_runtime/mix.exs @@ -7,8 +7,8 @@ defmodule Lambda.MixProject do def project do [ app: :aws_lambda_elixir_runtime, - version: "0.1.0", - elixir: "~> 1.7", + version: "0.2.0", + elixir: "~> 1.15", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, deps: deps(), @@ -17,7 +17,8 @@ defmodule Lambda.MixProject do # Docs name: "AWS Lambda Elixir Runtime", source_url: "https://github.com/aws-samples/aws-lambda-elixir-runtime", - homepage_url: "https://github.com/aws-samples/aws-lambda-elixir-runtime/tree/master/elixir_runtime", + homepage_url: + "https://github.com/aws-samples/aws-lambda-elixir-runtime/tree/master/elixir_runtime", docs: [ source_url_pattern: "https://github.com/aws-samples/aws-lambda-elixir-runtime/blob/master/elixir_runtime/%{path}#L%{line}", @@ -34,16 +35,17 @@ defmodule Lambda.MixProject do def application do [ mod: {ElixirRuntime.Application, []}, - extra_applications: [:logger, :inets] + extra_applications: [:logger, :inets, :ssl] ] end # Run "mix help deps" to learn about dependencies. defp deps do [ - {:poison, "~> 3.1"}, - {:mox, "~> 0.4", only: :test}, - {:ex_doc, "~> 0.19", only: :dev, runtime: false} + {:jason, "~> 1.4"}, + {:mox, "~> 1.0", only: :test}, + {:ex_doc, "~> 0.27", only: :dev, runtime: false}, + {:castore, "~> 1.0", only: [:dev, :test]} ] end diff --git a/elixir_runtime/priv/libcrypto.so.1.0.0 b/elixir_runtime/priv/libcrypto.so.1.0.0 deleted file mode 100644 index 0f177da..0000000 Binary files a/elixir_runtime/priv/libcrypto.so.1.0.0 and /dev/null differ diff --git a/elixir_runtime/priv/libcrypto.so.1.1 b/elixir_runtime/priv/libcrypto.so.1.1 new file mode 100755 index 0000000..36af4bd Binary files /dev/null and b/elixir_runtime/priv/libcrypto.so.1.1 differ diff --git a/elixir_runtime/priv/libssl.so b/elixir_runtime/priv/libssl.so index 93d5e91..1fb4345 100644 Binary files a/elixir_runtime/priv/libssl.so and b/elixir_runtime/priv/libssl.so differ diff --git a/elixir_runtime/test/elixir_runtime/monitor/state_test.exs b/elixir_runtime/test/elixir_runtime/monitor/state_test.exs index 2112005..92263a3 100644 --- a/elixir_runtime/test/elixir_runtime/monitor/state_test.exs +++ b/elixir_runtime/test/elixir_runtime/monitor/state_test.exs @@ -38,7 +38,7 @@ defmodule ElixirRuntime.Monitor.State.Test do test "report an error from initial state" do reason = {:badarg, []} - expected = Poison.encode!(Monitor.Error.from_exit_reason(:runtime, reason)) + expected = Jason.encode!(Monitor.Error.from_exit_reason(:runtime, reason)) State.initial(FakeClient) |> State.error(reason) @@ -48,7 +48,7 @@ defmodule ElixirRuntime.Monitor.State.Test do test "report an error while an invocation is in progress" do invoke_id = "fakeid" reason = {:badarg, []} - expected = Poison.encode!(Monitor.Error.from_exit_reason(:function, reason)) + expected = Jason.encode!(Monitor.Error.from_exit_reason(:function, reason)) State.initial(FakeClient) |> State.start_invocation(invoke_id) diff --git a/elixir_runtime/test/support/fake_invoke.ex b/elixir_runtime/test/support/fake_invoke.ex index e6c3537..2e0fe7e 100644 --- a/elixir_runtime/test/support/fake_invoke.ex +++ b/elixir_runtime/test/support/fake_invoke.ex @@ -9,7 +9,7 @@ defmodule Support.FakeInvoke do def with_message(message) do body = %{msg: message} - {generated_id(), Poison.encode!(body), %{}} + {generated_id(), Jason.encode!(body), %{}} end def id({id, _body, _context}), do: id diff --git a/examples/hello_world/config/config.exs b/examples/hello_world/config/config.exs index 2e34aab..0944882 100644 --- a/examples/hello_world/config/config.exs +++ b/examples/hello_world/config/config.exs @@ -1,4 +1,4 @@ # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 -use Mix.Config +import Config diff --git a/examples/hello_world/config/runtime.exs b/examples/hello_world/config/runtime.exs new file mode 100644 index 0000000..04cf944 --- /dev/null +++ b/examples/hello_world/config/runtime.exs @@ -0,0 +1,11 @@ +import Config + +# config/runtime.exs is executed for all environments, including +# during releases. It is executed after compilation and before the +# system starts, so it is typically used to load production configuration +# and secrets from environment variables or elsewhere. Do not define +# any compile-time configuration in here, as it won't be applied. +# The block below contains prod specific runtime configuration. +if config_env() == :prod do + +end diff --git a/examples/hello_world/mix.exs b/examples/hello_world/mix.exs index ca28524..418f393 100644 --- a/examples/hello_world/mix.exs +++ b/examples/hello_world/mix.exs @@ -7,10 +7,19 @@ defmodule HelloWorld.MixProject do def project do [ app: :hello_world, - version: "0.1.0", - elixir: "~> 1.7", + version: "0.2.0", + elixir: "~> 1.15", start_permanent: Mix.env() == :prod, - deps: deps() + deps: deps(), + releases: [ + hello_world: [ + version: "0.2.0", + applications: [hello_world: :permanent, aws_lambda_elixir_runtime: :permanent], + include_erts: true, + include_executables_for: [:unix], + + ] + ] ] end @@ -24,8 +33,7 @@ defmodule HelloWorld.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:aws_lambda_elixir_runtime, path: "../../elixir_runtime"}, - {:distillery, "~> 2.0"} + {:aws_lambda_elixir_runtime, path: "../../elixir_runtime"} ] end end diff --git a/examples/hello_world/rel/config.exs b/examples/hello_world/rel/config.exs deleted file mode 100644 index 42274c5..0000000 --- a/examples/hello_world/rel/config.exs +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: MIT-0 - -~w(rel plugins *.exs) -|> Path.join() -|> Path.wildcard() -|> Enum.map(&Code.eval_file(&1)) - -use Mix.Releases.Config, - default_release: :hello_world, - default_environment: :lambda - -environment :lambda do - set include_erts: true - set include_src: false - set cookie: :test - set include_system_libs: true - - # Distillery forces the ERTS into 'distributed' mode which will - # attempt to connect to EPMD. This is not supported behavior in the - # AWS Lambda runtime because our process isn't allowed to connect to - # other ports on this host. - # - # So '-start_epmd false' is set so the ERTS doesn't try to start EPMD. - # And '-epmd_module' is set to use a no-op implementation of EPMD - set erl_opts: "-start_epmd false -epmd_module Elixir.EPMD.StubClient" -end - -release :hello_world do - set version: current_version(:hello_world) - set applications: [ - :runtime_tools, :aws_lambda_elixir_runtime - ] -end diff --git a/examples/hello_world/rel/env.bat.eex b/examples/hello_world/rel/env.bat.eex new file mode 100644 index 0000000..0d82afd --- /dev/null +++ b/examples/hello_world/rel/env.bat.eex @@ -0,0 +1,8 @@ +@echo off +rem Set the release to load code on demand (interactive) instead of preloading (embedded). +rem set RELEASE_MODE=interactive + +rem Set the release to work across nodes. +rem RELEASE_DISTRIBUTION must be "sname" (local), "name" (distributed) or "none". +rem set RELEASE_DISTRIBUTION=name +rem set RELEASE_NODE=<%= @release.name %> diff --git a/examples/hello_world/rel/env.sh.eex b/examples/hello_world/rel/env.sh.eex new file mode 100644 index 0000000..ab7b76a --- /dev/null +++ b/examples/hello_world/rel/env.sh.eex @@ -0,0 +1,20 @@ +#!/bin/sh + +# # Sets and enables heart (recommended only in daemon mode) +# case $RELEASE_COMMAND in +# daemon*) +# HEART_COMMAND="$RELEASE_ROOT/bin/$RELEASE_NAME $RELEASE_COMMAND" +# export HEART_COMMAND +# export ELIXIR_ERL_OPTIONS="-heart" +# ;; +# *) +# ;; +# esac + +# # Set the release to load code on demand (interactive) instead of preloading (embedded). +# export RELEASE_MODE=interactive + +# # Set the release to work across nodes. +# # RELEASE_DISTRIBUTION must be "sname" (local), "name" (distributed) or "none". +# export RELEASE_DISTRIBUTION=name +# export RELEASE_NODE=<%= @release.name %> diff --git a/examples/hello_world/rel/remote.vm.args.eex b/examples/hello_world/rel/remote.vm.args.eex new file mode 100644 index 0000000..983397a --- /dev/null +++ b/examples/hello_world/rel/remote.vm.args.eex @@ -0,0 +1,8 @@ +## Customize flags given to the VM: https://www.erlang.org/doc/man/erl.html +## -mode/-name/-sname/-setcookie are configured via env vars, do not set them here + +## Increase number of concurrent ports/sockets +##+Q 65536 + +## Tweak GC to run more often +##-env ERL_FULLSWEEP_AFTER 10 diff --git a/examples/hello_world/rel/vm.args.eex b/examples/hello_world/rel/vm.args.eex new file mode 100644 index 0000000..d6553e2 --- /dev/null +++ b/examples/hello_world/rel/vm.args.eex @@ -0,0 +1,16 @@ +## Customize flags given to the VM: https://www.erlang.org/doc/man/erl.html +## -mode/-name/-sname/-setcookie are configured via env vars, do not set them here + +## Increase number of concurrent ports/sockets +##+Q 65536 + +## Tweak GC to run more often +##-env ERL_FULLSWEEP_AFTER 10 + +# Running ERTS in distributed mode will attempt to connect to EPMD. +# This is not supported behavior in the AWS Lambda runtime because our +# process isn't allowed to connect to other ports on this host. +# +# So '-start_epmd false' is set so the ERTS doesn't try to start EPMD. +# And '-epmd_module' is set to use a no-op implementation of EPMD +-start_epmd false -epmd_module Elixir.EPMD.StubClient