View Source Venomous (Venomous v0.7.7)
A wrapper around erlport python Ports, designed to simplify concurrent use. It focuses dynamic extensibility, like spawning, reusing and killing processes on demand. Furthermore, unused processes get automatically cleaned up by a scheduled process which can be configured inside config.exs. Venomous core functions capture and handle :EXIT calls ensuring that all python process die with it and do not continue their execution.
The core concept revolves around "Snakes" which represent Python worker processes. These Venomous.SnakeWorker
are managed and
supervised with Venomous.SnakeManager
GenServer to allow concurrent and efficient execution of Python code. The Snakes
pids
and python pids are stored inside :ets
table and the Processes are handled by DymanicSupervisor
called Venomous.SnakeSupervisor
.
The unused Snakes
get automatically killed by SnakeManager
depending on the given configuration.
You can checkout examples here
Be sure to also check the README
Main Functionality
Basic processes
These are automatically managed and made for concurrent operations
python/2
|python/1
: The primary function to execute a Python function. IfVenomous.SnakeWorker
is free it retrieves it and runs the specifiedVenomous.SnakeArgs
returning the result/errorpython!/2
|python!/1
: Will wait until anyVenomous.SnakeWorker
is freed, requesting it with the given interval.
Named processes
Python processes with unique names not managed by Venomous.SnakeManager
. These do not get cleaned-up and stay for as long as they are not killed
adopt_snake_pet/2
: Creates a newVenomous.SnakeWorker
with a name insideVenomous.PetSnakeSupervisor
pet_snake_run/3
: Runs givenVenomous.SnakeArgs
inside the named python process
Architecture
Venomous consists of several key components:
Venomous.SerpentWatcher
: Manages hot reloading.Venomous.SnakeWorker
: Manages the execution of Python processes.Venomous.SnakeSupervisor
: A DynamicSupervisor that oversees the SnakeWorkers.Venomous.SnakeManager
: A GenServer that coordinates the SnakeWorkers and handles operations like spawning, retrieval and cleanup.Venomous.PetSnakeSupervisor
: Similar to SnakeSupervisor but for named processes.Venomous.PetSnakeManager
: Manages named python processes calls
Configuration Options
Venomous
# The way to kill python process on an OS level. :polite for SIGTERM | :brutal for SIGKILL. Anything else does not run kill
:venomous, :termination_style, :polite
SnakeManager
The behavior and management of Snakes can be configured through the following options:
:venomous, :snake_manager, %{
snake_ttl_minutes: non_neg_integer(), # Time-to-live for a Snake in minutes. Default is 15 min.
perpetual_workers: non_neg_integer(), # Number of Snakes to keep alive perpetually. Default is 10.
cleaner_interval: non_neg_integer(), # Interval in milliseconds for cleaning up inactive Snakes. Default is 60_000 ms.
erlport_encoder: %{module: atom(), func: atom(), args: list(any())}, # Optional :erlport encoder/decoder python function for converting types. This function is applied to every unnamed python process started by SnakeManager. For more information see [Handling Erlport API](PYTHON.md)
}
SnakeManager options
All of these are optional. However you will most likely want to set module_paths for python processes
config :venomous, :snake_manager, %{
# Optional :erlport encoder/decoder for type conversion between elixir/python applied to all workers. The function may also include any :erlport callbacks from python api
erlport_encoder: %{
module: :my_encoder_module,
func: :encode_my_snakes_please,
args: []
},
python_opts: [
module_paths: [], # List of paths to your python module files.
cd: "", # Change python's directory on spawn. Default is $PWD
compressed: 0, # Can be set from 0-9. May affect performance. Read more on [Erlport documentation](http://erlport.org/docs/python.html#erlang-api)
envvars: [], # additional python process envvars
packet_bytes: 4, # Size of erlport python packet. Default: 4 = max 4GB of data. Can be set to 1 = 256 bytes or 2 = ? bytes if you are sure you won't be transfering a lot of data.
python_executable: "" # path to python executable to use. defaults to PATH
]
# TTL whenever python process is inactive. Default: 15
snake_ttl_minutes: 15,
# Number of python workers that don't get cleared by SnakeManager when their TTL while inactive ends. Default: 10
perpetual_workers: 10,
# Interval for killing python processes past their ttl while inactive. Default: 60_000ms (1 min)
cleaner_interval: 60_000,
# reload module for hot reloading.
# default is already provided inside venomous python/ directory
reload_module: :reload,
}
Hot reloading
Requires watchdog
python module, which can be installed with mix venomous.watchdog install
.
Only files inside module_paths
config are being watched.
config :venomous, :serpent_watcher, [
enable: false,
logging: true, # logs every hot reload
module: :serpent_watcher, # Provided by default
func: :watch_directories, # Provided by default
manager_pid: Venomous.SnakeManager, # Provided by default
]
Struct/Class comp
Venomous provides an easy way to convert structs into classes and back with VenomousTrait class and mix venomous.structs ...
task.
$ mix venomous.structs
Simple utility to create python elixir compatible classes.
VenomousTrait class provides 2 functions:
- def from_dict(cls, erl_map: Map | Dict, structs: Dict = {}) -> cls
# converts Erlport Map or a Dict into the object class
- def into_erl(self) -> Map
# returns erlang compatible struct from self
To create basic python classes and encode/decode functions based on structs:
- mix venomous.structs MyModule.MyStruct MyModule.MoreStructs ...
To create extended classes depending on existing python class:
- mix venomous.structs MyModule.MyStruct:PythonClassName ...
To create for all available structs inside an application
- mix venomous.structs all my_application
You can see this used in the struct_test.exs and test_venomous.py
Auxiliary Functions
list_alive_snakes/0
: Returns a list of :ets table containing currently alive Snakes.clean_inactive_snakes/0
: Manually clears inactive Snakes depending on their ttl and returns the number of Snakes cleared.slay_python_worker/2
: Kills a specified Python worker process and its SnakeWorker. :brutal or :polite can be specified as option, which willkill -9
orkill -15
the os process of python which prevents the code from executing until it finalizes or goes through iteration.slay_pet_worker/2
: Kills a named Python processretrieve_snake/0
: Retrieves aVenomous.SnakeWorker
and sets its status to :retrievedget_snakes_ready/1
: Retrieves given amount ofVenomous.SnakeWorker
s
Summary
Functions
Creates a named Venomous.SnakeWorker
inside Venomous.PetSnakeSupervisor
Clears inactive snakes manually, returns number of snakes cleared.
Retrieves x amount of ready snakes and sets their status to :retrieved. In case of hitting max_children cap, stops and returns all available snakes.
Returns list of :ets table containing alive snakes
Used to run given Venomous.SnakeArgs
inside named snake
Does not handle :EXIT signals like snake_run/2
does.
If pet snake with name does not exist will return :not_found
Preloads amount
of workers with :ready state
Wrapper for calling python process
Tries to retrieve Venomous.SnakeWorker
which then runs the given Venomous.SnakeArgs
. In case of failure will return {:retrieve_error, message}.
In case :EXIT happens, it will kill python os process along with its worker and exit(reason)
If no Snake is available will continue requesting it with the given interval until any gets freed or receives :EXIT signal
Retrieves Venomous.SnakeWorker
struct and sets it's status to :retrieved preventing other processes from accessing it.
If all processes are busy and exceeds max_children will return {:retrieve_error, message}.
If all processes are busy and exceeds max_children will wait for interval ms and try again. Traps the exit signals, to safely escape loop.
Kills the named python process :brutal also kills the OS process of python, ensuring the process does not continue execution.
Kills python process and its SnakeWorker :brutal also kills the OS process of python, ensuring the process does not continue execution.
Runs Venomous.SnakeArgs
inside given Venomous.SnakeWorker
.
Traps exit and awaits signals [:SNAKE_DONE, :SNAKE_ERROR, :EXIT]
In case of an exit, brutally kills the python process ensuring it doesn't get executed any further.
Functions
Creates a named Venomous.SnakeWorker
inside Venomous.PetSnakeSupervisor
Parameters
- an atom() name.
- opts for python process
Options
Python options can be configured inside :venomous :python_opts config key
All of these are optional. However you will most likely want to set module_paths
erlport_encoder: %{module: atom(), func: atom(), args: list(any())}
: Optional :erlport encoder/decoder python function for converting types. This function is applied to every unnamed python process started by SnakeManager. For more information see Handling Erlport API@available_opts [ :module_paths, # List of paths to your python modules :cd, # Change python's directory on spawn. Default is $PWD :compressed, # Can be set from 0-9. May affect performance. Read more on [Erlport documentation](http://erlport.org/docs/python.html#erlang-api) :envvars, # additional python process envvars :packet_bytes, # Size of erlport python packet. Default: 4 = max 4GB of data. Can be set to 1 = 256 bytes or 2 = ? bytes if you are sure you won't be transfering a lot of data. :python_executable # path to python executable to use. ]
Returns
- :ok, name - in case of success
- :error, message - in case of failure
@spec clean_inactive_snakes() :: non_neg_integer()
Clears inactive snakes manually, returns number of snakes cleared.
@spec get_snakes_ready(non_neg_integer()) :: [Venomous.SnakeWorker.t()]
Retrieves x amount of ready snakes and sets their status to :retrieved. In case of hitting max_children cap, stops and returns all available snakes.
Warning
In case of retrieving all available snakes and not using them right away, functions like
python!/2
andretrieve_snake!/0
will loop until they are freed.
Parameters
- amount of snakes to retrieve
Returns
- A list of
Venomous.SnakeWorker
structs
@spec list_alive_snakes() :: [{pid(), pid(), non_neg_integer(), atom(), any()}]
Returns list of :ets table containing alive snakes
@spec pet_snake_run(Venomous.SnakeArgs.t(), name :: atom(), timeout()) :: any() | :not_found | {:error, :timeout}
Used to run given Venomous.SnakeArgs
inside named snake
Does not handle :EXIT signals like snake_run/2
does.
If pet snake with name does not exist will return :not_found
Preloads amount
of workers with :ready state
@spec python( Venomous.SnakeArgs.t(), keyword() ) :: any()
Wrapper for calling python process
Tries to retrieve Venomous.SnakeWorker
which then runs the given Venomous.SnakeArgs
. In case of failure will return {:retrieve_error, message}.
In case :EXIT happens, it will kill python os process along with its worker and exit(reason)
Parameters
Venomous.SnakeArgs
struct of :module, :func, :args- opts \ []
Opts
:python_timeout
ms timeout. Kills python OS process on timeout. Default: 15_000:kill_python_on_exception
Should python process be killed on exception. Should be set to true if your python process exits by itself. Default: false
Returns
any() | {:error, :timeout} | {:killed, reason} | {retrieve_error: any()} retrieves output of python function or error
@spec python!( Venomous.SnakeArgs.t(), keyword() ) :: any()
If no Snake is available will continue requesting it with the given interval until any gets freed or receives :EXIT signal
Opts
:retrieve_interval
ms to wait before requesting snake again Default: 200:python_timeout
ms timeout. Kills python OS process on timeout. Default: 15_000:kill_python_on_exception
Should python process be killed on exception. Should be set to true if your python process exits by itself. Default: false
@spec retrieve_snake() :: {:retrieve_error, reason :: term()} | Venomous.SnakeWorker.t()
Retrieves Venomous.SnakeWorker
struct and sets it's status to :retrieved preventing other processes from accessing it.
If all processes are busy and exceeds max_children will return {:retrieve_error, message}.
Returns
Venomous.SnakeWorker
struct. In case of error{:retrieve_error, message}
@spec retrieve_snake!(non_neg_integer()) :: Venomous.SnakeWorker.t()
If all processes are busy and exceeds max_children will wait for interval ms and try again. Traps the exit signals, to safely escape loop.
Parameters
- interval: The time to wait in milliseconds before retrying. Default is
@wait_for_snake_interval
.
Returns
Venomous.SnakeWorker
struct.
Kills the named python process :brutal also kills the OS process of python, ensuring the process does not continue execution.
Parameters
name
atom- a Way to kill process. :brutal additionally kills with kill -9 ensuring the python does not execute further. Default: :peaceful
Returns
:ok
@spec slay_python_worker(Venomous.SnakeWorker.t(), termination_style :: atom()) :: :ok
Kills python process and its SnakeWorker :brutal also kills the OS process of python, ensuring the process does not continue execution.
Parameters
Venomous.SnakeWorker
struct- a Way to kill process. :brutal additionally kills with kill -9 ensuring the python does not execute further. Default: :peaceful
Returns
:ok
@spec snake_run(Venomous.SnakeArgs.t(), Venomous.SnakeWorker.t(), keyword()) :: any()
Runs Venomous.SnakeArgs
inside given Venomous.SnakeWorker
.
Traps exit and awaits signals [:SNAKE_DONE, :SNAKE_ERROR, :EXIT]
In case of an exit, brutally kills the python process ensuring it doesn't get executed any further.
Parameters
Venomous.SnakeArgs
struct of :module, :func, :argsVenomous.SnakeWorker
struct- opts Keywords
Opts
:python_timeout
ms timeout. Kills python OS process on timeout. Default: 15_000:kill_python_on_exception
Should python process be killed on exception. Should be set to true if your python process exits by itself. Default: false
Returns
any() | {:error, :timeout} | {:killed, reason} | %SnakeError{} - retrieves output of python function or error