Elixir/Erlang: Communication with external process - erlang

Say I have a simple python script which executes an elixir/erlang script using the subprocess module.
Say the OS PID of the python script is P1 and that of the spawned elixir/erlang script running is P2.
I want to know if communication between P1 and P2 is possible. More specifically, P1 writes something to the stdin of P2, and P2 reads the received input from P1 and writes some corresponding output to its own stdout and P1 reads from the stdout of P2 and again writes something to the stdin of P2 and so on.
I know the other way is possible, i.e., spawning external process from inside elixir/erlang and then communicating with the process. Any help appreciated, thanks.

Yep, this sort of cross-language IPC is entirely possible. The vast majority of the documentation and blog posts and such (and the responses so far here on StackOverflow!) assume the opposite of what you seem to be asking - that is, they assume that Erlang/Elixir is spawning the Python subprocess, rather than Python spawning an Erlang/Elixir subprocess. If that's okay (i.e. you're okay with your Erlang or Elixir app spinning up the Python process), then great! Badu's answer will help you do exactly that, and you could also have a gander at the documentation for Elixir's Port module for an extra reference.
But that doesn't seem to be the answer you seek, and that's less fun. The world needs more documentation on how to go the other way around, so let's dive into the wonderful world of running Erlang as a subprocess of a Python script!
First, our Python script (eip.py):
#!/usr/bin/env python
from subprocess import Popen, PIPE
erl = Popen(['escript', 'eip.escript'],
stdin=PIPE, stdout=PIPE, stderr=PIPE)
ping = input('Ping: ')
outs, errs = erl.communicate(input=ping.encode('utf-8'),
timeout=15)
print(outs.decode('utf-8'))
On the Erlang side (as you might've noticed in that Python code), a really easy way to go about this is to use the escript program, which allows us to write more-or-less self-contained Erlang scripts, like this here eip.escript:
#!/usr/bin/env escript
main(_Args) ->
Ping = io:get_line(""),
io:format("Pong: ~ts", [Ping]).
Now, when you run python3 eip.py and enter asdf at the Ping: prompt, you should get back Pong: asdf.
Doing the same thing with Elixir is only slightly more complicated: we need to create a Mix project with a bit of extra configuration and such to tell Mix to put together an escript file. So let's start with the project:
$ mix new eip
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/eip.ex
* creating test
* creating test/test_helper.exs
* creating test/eip_test.exs
Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:
cd eip
mix test
Run "mix help" for more commands.
(It's probably overkill to even use Mix for this simple example, but I'm assuming you'll eventually want to do something more advanced than this example)
Next, you'll want to add an escript option to your mix.exs, like so:
defmodule Eip.MixProject do
use Mix.Project
def project, do: [
app: :eip,
version: "0.1.0",
elixir: "~> 1.9",
start_permanent: Mix.env() == :prod,
deps: deps(),
escript: escript()
]
def application, do: [extra_applications: [:logger]]
defp deps, do: []
defp escript, do: [main_module: Eip]
end
And finally, your lib/eip.ex module:
defmodule Eip do
def main(_argv) do
ping = IO.gets("")
IO.puts("Pong: #{ping}")
end
end
And now we just need to build it:
$ mix escript.build
Compiling 1 file (.ex)
Generated eip app
Generated escript eip with MIX_ENV=dev
eip.py will need a slight adjustment to point to this new Elixirified ping/pong IPC thingamabob:
#!/usr/bin/env python
from subprocess import Popen, PIPE, TimeoutExpired
erl = Popen(['escript', 'eip/eip'],
stdin=PIPE, stdout=PIPE, stderr=PIPE)
ping = input('Ping: ')
outs, errs = erl.communicate(input=ping.encode('utf-8'))
print(outs.decode('utf-8'))
Unfortunately, this doesn't entirely work:
$ python3 eip.py
Ping: asdf
Pong: eof
The same results happen even when using a more direct port of the Erlang version (i.e. replacing IO.gets("") with :io.get_line("") and IO.puts("Pong: #{ping}") with :io.fwrite("Pong: ~ts", [ping]), which means something specific to Elixir's STDIN handling in general is causing it to prematurely believe it's reached end-of-file. But hey, at least one direction works!

Like Dogbert said, you can use Ports instead. Check out Erlport
and here is a blog post on communicating between Elixir and Python

Related

How can I build an Elixir escript that does not halt the Erlang VM after execution (like elixir --no-halt)

I have a program that starts the application and then adds (children) workers to a supervisor. Obviously after doing only that it has nothing more left to do and it halts (exits). So making it not halt the VM would allow the workers to work.
The only solution I have came up was to add:
IO.gets "Working... To finish hit <Enter>."
at the end...
I want to build an escript that after running will not halt the Erlang VM just like:
elixir --no-halt -S mix run --eval 'MyApp.CLI.m
ain(["some-arg"])'
or
mix run --no-halt --eval 'MyApp.CLI.m
ain(["some-arg1,some-arg2"])'
Is there a way to do this with escript?
Or should I use a different solution to pack and distribute my program that is actually more like a server/daemon than a command line tool?
A typical approach to packaging such systems is an OTP release. You can use exrm for that.
If for some reasons, you still want to use escript, you can just call :timer.sleep(:infinity) after you start all the applications and processes.
NOTE: Starting from Elixir 1.9
We can use System.no_halt(true) to allow script to never stop.
Here is simple script example:
defmodule Mix.Tasks.NoHalt do
use Mix.Task
def run(_) do
System.no_halt(true)
IO.puts("Never die!")
end
end

How do I start applications by command line as a daemon?

This has been my current routine
sudo nohup erl -sname foo -pa ./ebin -run foo_supervisor shell -noshell -noinput &
where the shell function looks something like this
shell() ->
{ok, Pid} = supervisor:start_link({local,?MODULE}, ?MODULE, _Arg = []),
unlink(Pid).
If I don't unlink from shell it immediately stops for some reason. Is there a way I can just start my application like I would normally ie application:start(foo). Also what if I want to start sasl too? Also where could I learn more about making a self contained package using rebar?
Preface. About your unlink
In this other SO thread #filippo explains why you need the unlink when testing supervisors from the shell.
First. What you need is an Erlang application.
Reading from the doc:
In OTP, application denotes a
component implementing some specific
functionality, that can be started and
stopped as a unit, and which can be
re-used in other systems as well.
Details on how to implement an Erlang application are available here. The three main things you will need to do are:
Have a proper directory structure for your application
Write an application callback module implementing the Erlang application behaviour. That's where you will start your root supervisor
Provide an application resource file. This is where you tell the system - among other things - where to find your application callback module (look at the mod parameter).
Second. Starting SASL.
In the above application resource file, you can specify a list of applications you want to start before your application. You will add something like:
...
{applications, [kernel, stdlib, sasl]},
...
To tell it to start SASL.
Third. Rebar.
There's an introduction to Rebar here, which explains you how to use Rebar to help you in the above steps, to pack your brand new application into an Erlang release and how to start it.

Run erlang application without terminal depending

I have erlang application: *.app file and some *.erl files. I compile all of them. In terminal i start erl and there application:start(my_application)., all ok, but if i closed terminal application close too. How can i run application without terminal depending?
Thank you.
You likely want to use the -noshell option to erl. The syntax is
erl -noshell -s Module Function Arguments
So in your case, this might be
erl -noshell -s application start my_application
This should allow you (for example if you are on Unix/Linux) to start your application as a background process and leave it running.
One useful variation is to also call the stop/0 function of the init module so that the Erlang environment will stop when it has finished running your function. This comes in handy if you want to run a simple one-use function and pipe the output to some other process.
So, for example, to pipe to more you could do
erl -noshell -s mymodule myfunction -s init stop | more
Finally, you might also be able to use the escript command to run your Erlang code as scripts rather than compiled code if it makes sense for your situation.
Hope that helps.
The proper way to handle this situation, is building a release containing your app and running the system as so called embedded one.
This release is going to be completely independent (it will hold erts and all the libs like, kernel, std, mnesia etc.).
On start, the new process will not be connected to shell process.
It will be OS process, so you can attach to it with pipes. All script are included in OTP.
Here is some info: http://www.erlang.org/doc/design_principles/release_structure.html
It may seem to be complicated, but tools like rebar do everything for you.

How to run Erlang from unix shell with complex params?

I need to run complex Erlang module function from unix shell
rpc:call('node#example.com', mnesia, dirty_first, [mytable])
how can i do it?
UPD:
i make test.escript
chmod +x test.escript
#!/usr/lib64/erlang/bin/escript
%%! -name 'test#example.com'
main(_Args) ->
R = rpc:call('node#example.com', mnesia, dirty_first, [mytable]),
io:format("~p~n",[R]).
and receive {badrpc, nodedown}
but when run
erl -name test#example.com
1> rpc:call('node#example.com', mnesia, dirty_first, [mytable]).
{my, data}.
I mean it works, but howto make escript work proprely?
I think escript might be something worth looking into.
Edit:
Some examples.
First for all examples: Start the remote node somewhere, somehow.
dannib#duval:~:> erl -sname bar
(bar#duval)1> erlang:get_cookie().
'KNKKCFPYMJUPIOLYPOAA'
Escript
1: Create a file named hello.escript with content
#!/usr/bin/env escript
%%! -sname foo#duval -setcookie KNKKCFPYMJUPIOLYPOAA
main(_String) ->
Node = 'bar#duval',
Mod = 'erlang',
Fun = 'node',
Args = [],
R = rpc:call(Node, Mod, Fun, Args),
io:format("Hello there ~p~n",[R]).
Notice that the %%! -sname foo#bar identifies the node on the host (instead of creating nonode#nohost), allow setting the same cookie %%! -sname foo#duvel -setcookie KNKKCFPYMJUPIOLYPOAA as target host which solves the problem of getting {badrpc,nodedown}. Notice that the same statement holds for the following examples (erl_call, and -eval) where both the node name and cookie is set.
2: Set the execution bit and run
$ chmod +x hello.escript
$ ./hello.escript
Hello there bar#duval
Erl_call
1: run
$ erl_call -c 'KNKKCFPYMJUPIOLYPOAA' -a 'erlang node' -n bar#duval
bar#duval
Eval
1: run
$ erl -sname foo -setcookie 'KNKKCFPYMJUPIOLYPOAA'
-eval 'io:format("Hello there ~p~n",[rpc:call(bar#duval,erlang, node, [])])'
... Eshell V5.7.4 (abort with ^G)
(foo#duval)1> Hello there bar#duval
This creates a shell which might not be what you want in this case.
I might mention that if both nodes are on the same host and using the same cookie default value, the cookie value for foo and bar don't have to be explicitly set like in the examples.
After doing these examples and reading your question again I think what I GIVE TERRIBLE ADVICE said will be your best choice, erl_call. I fell for the word "complex" in question title where imho escripts allow much more "complex" setups in a easy-to-read manner. The variable _String in the escript example holds the arguments to the script which allows you to both access input through shell and perform complex erlang operations in the EVM. But erl_call might be more straight forward if you already have logic in some other application and just need to make this simple call to an erlang node.
The erl_call application is exactly what you need:
erl_call makes it possible to start and/or communicate with a distributed Erlang node. It is built upon the erl_interface library as an example application. Its purpose is to use an Unix shell script to interact with a distributed Erlang node. It performs all communication with the Erlang rex server, using the standard Erlang RPC facility. It does not require any special software to be run at the Erlang target node.
The main use is to either start a distributed Erlang node or to make an ordinary function call. However, it is also possible to pipe an Erlang module to erl_call and have it compiled, or to pipe a sequence of Erlang expressions to be evaluated (similar to the Erlang shell).
See the examples for more details
You can use -eval flag of erl:
$ erl -eval 'io:format("Hello, World!~n")'
You can parse complex arguments with escript:
#!/usr/bin/env escript
main(String) ->
{Node, Mod, Fun, Args} = parse_args(String),
R = rpc:call(Node, Mod, Fun, Args),
io:format("~p~n",[R]).
If your problem is how to set the Erlang node in network mode (i.e. turn the node into a distributed node), you might want to do something like
EPMD = code:root_dir() ++ "/bin/epmd &",
os:cmd(EPMD),
net_kernel:start([Sname, shortnames])
where Sname is your wanted node name. Only after this can you start communicating to another node with e.g. rpc.

erlang - startup script

To start my program I do the next sequence:
$ erl
> c(module1).
> c(module2).
> c(modulen).
modulen:start().
Is there any possibility to create script that allow me to launch my program ?
You could use a loader script that takes care of starting your application in an OTP fashion:
-module(XYZ_app).
-export([start/0]).
start() ->
application:start(inets),
application:start(XYZ).
You launch that script through a shell script. Beware of using escript if you plan on building daemons running at the OS boot time as they are tricky.
#!/bin/bash
erl -boot start_sasl -s XYZ_app start
Of course you need your XYZ.app file (just an example):
{application, tinycouch,
[{description, "tinycouch"},
{vsn, "0.1"},
{modules, [
tinycouch, tinycouch_app, tinycouch_sup,
tinycouch_server, tinycouch_logger_h, tinycouch_utils,
tinycouch_db, mod_tinycouch
]},
{registered, [tinycouch
,tinycouch_server
,tinycouch_sup
]},
{applications, [kernel, stdlib, sasl, inets]},
{env, []},
%% Application Start point
{mod, {tinycouch_sup, []}}]}.
... and all your .erl files must have been compiled.
Note that if you intend to distribute your application (e.g. Debian repository etc), you probably should consider having a make file to compile and install your app.
As a final note: you could also go with a boot file (the ultimate OTP way) but I find those very constraining: a boot file ties your application to specific version release of Erlang (and other dependent applications). Your application might be able to run in various Erlang releases but you will need to have a separate build/release process for each "platform release" you intend to ship for.
You can compile the modules with erlc
erlc module1 module2 module3
You can also create a script to run you program
#!/usr/bin/escript
main(_) ->
modulen:start().
Then in the console:
chmod +x start.erl
./start.erl
Two ways
precompile your code
$ erlc module1.erl
$ erl -s module1 funcname arg1 arg2 ....
Or use escript
#!/usr/bin/env escript
main() ->
module1:fun(...)
escript is a scripting interface to Erlang
You can use erl -make directly, as it assumes all *.erl files should be compiled to *.beam, and skip it if they haven't been changed since last compile. Check out the make module for what you can put in your Emakefile to change certain behavior.
Using plain Makefile to invoke this compile strategy also gives other users a familiar build-procedure.
Having a bootstrap start function as jldupont suggests is a nice way to start up your development environment. It is even sufficiently fine for using in production to some point.
The next step is to use full OTP (*.rel) release files and generate boot scripts that starts all the applications in the order the *.app specify dependencies on each other.
In the home directory, create file
.erlang
in that file write
compile:file(/path-to/module1).
compile:file(/path-to/module2).
compile:file(/edit-real-path-to/modulen). % etcetera; do not use c(module) inside this file!
then, if you need add (inside .erlang):
module1:start(). % etc
When you run erl (in the shell), the content of the .erlang (provided this file is in the home dir) will be executed at start (of the interpreter-erlang-VM you launch).
You can create .erlang file in some other (some 'current') directory, then, if you run erl (erlang interpreter) in that directory, that another .erlang will override the content of that .erlang in the home dir (of that which is like /home/your-user-name/.erlang ).
It is good idea to write inside of .erlang some reminder, like
io:format(".erlang loaded-compiled module1!\n"). %etc
(as not to forget about that code which otherwise will be executed silently !)
(also, you can check, for this case, that e.g. module1 is indeed compiled and loaded after start, this or some similar way:
AllLoaded = code:all_loaded().
lists:filter(fun({Module,_})-> Module =:= module1 end, AllLoaded).
the answer should be like:
[{module1,"/home/your-user-name/(path-to-your-code)/module1.beam"}]
)
Check out Sinan and Faxien: http://www.erlware.org

Resources