Skip to content

Instantly share code, notes, and snippets.

@maxlapshin
Last active December 24, 2022 17:28
Show Gist options
  • Save maxlapshin/01773f0fca706acdcb4acb77d91d78bb to your computer and use it in GitHub Desktop.
Save maxlapshin/01773f0fca706acdcb4acb77d91d78bb to your computer and use it in GitHub Desktop.
Systemd support
-module(systemd).
% This is what you need to adopt systemd in erlang
%
% Do whatever you want license. If you want, you can take this code under terms of MIT license.
-export([ready/0, reloading/0, stopping/0, watchdog/0]).
-export([start_link/0]).
-export([init/1, handle_info/2, terminate/2]).
ready() -> call(<<"READY=1">>).
reloading() ->call(<<"RELOADING=1">>).
stopping() -> call(<<"STOPPING=1">>).
watchdog() -> call(<<"WATCHDOG=1">>).
call(Call) ->
case os:getenv("NOTIFY_SOCKET") of
false ->
{error, not_configured};
Path ->
case gen_udp:open(0, [local]) of
{error, SocketError} ->
{error, SocketError};
{ok, Socket} ->
Result = gen_udp:send(Socket, {local,Path}, 0, Call),
gen_udp:close(Socket),
Result
end
end.
start_link() ->
gen_server:start_link({local,?MODULE}, ?MODULE, [], []).
init([]) ->
erlang:send_after(60000, self(), watchdog),
{ok, state}.
handle_info(watchdog, State) ->
watchdog(),
erlang:send_after(60000, self(), watchdog),
{noreply, State}.
terminate(_,_) -> ok.
@mgpld
Copy link

mgpld commented Jul 26, 2018

Excellent !

@dch
Copy link

dch commented Jul 27, 2018

Just putting all the useful notes from Max's email in here so its all in 1 place:

While migrating to systemd from old good SysV init, I've found that it is a very good idea to speak to this daemon and tell him that my software is alive and ready. systemd authors offer library for this (and I suppose that they want to see these calls in POSIX and all other OS). Petr Lemenkov together with other commiters have written wrapper around this C library: https://github.com/systemd/erlang-sd_notify
I was completely against this idea, because I do not want to take nif around library that makes a very simple call, so I've decided to write a code that relies on protocol in this systemd library:

https://gist.github.com/maxlapshin/01773f0fca706acdcb4acb77d91d78bb

Just drop this file to your source, add systemd as a permanent worker for keepalive to work and don't forget to call systemd:ready to commit start.

Service file may look like this:

# systemd unit
[Unit]
Description=Retroview
After=network-online.target
Wants=network-online.target

[Service]
Environment=HOME=/var/lib/retroview
Environment=PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin
Environment=LANG=C
Environment=PROCNAME=retroview
Type=notify
User=root
Group=root
LimitNOFILE=102400
ExecStartPre=/bin/mkdir -p /var/lib/retroview
ExecStartPre=/bin/mkdir -p /var/log/retroview
ExecStart=/usr/bin/erl -p /opt/retroview/ebin -noinput -name retroview@127.0.0.1 -s retroview
Restart=on-failure
TimeoutStartSec=300s
WatchdogSec=120
WorkingDirectory=/opt/retroview
NotifyAccess=main

[Install]
WantedBy=multi-user.target

@joaohf
Copy link

joaohf commented Jul 27, 2018

+1

@dinama
Copy link

dinama commented Oct 3, 2019

small change for WATCHDOG_USEC support


-module(systemd).
-export([ready/0, reloading/0, stopping/0, watchdog/0]).
-export([start_link/0]).
-export([init/1, handle_info/2, terminate/2]).
-include_lib("kernel/include/logger.hrl").

ready() -> call(<<"READY=1">>).
reloading() ->call(<<"RELOADING=1">>).
stopping() -> call(<<"STOPPING=1">>).
watchdog() -> call(<<"WATCHDOG=1">>).


call(Call) ->
  ?LOG_INFO("systemd ~p", [Call]),
  case os:getenv("NOTIFY_SOCKET") of
    false -> {error, not_configured};
    Path ->
      case gen_udp:open(0, [local]) of
        {error, SocketError} ->
          {error, SocketError};
        {ok, Socket} ->
          Result = gen_udp:send(Socket, {local,Path}, 0, Call),
          gen_udp:close(Socket),
          Result
      end
  end.


start_link() ->
  gen_server:start_link({local,?MODULE}, ?MODULE, [], []).

init([]) ->
  WatchdogMs = case os:getenv( "WATCHDOG_USEC" ) of
    false -> none;
    Value ->
      Part = erlang:round(0.8 * erlang:list_to_integer(Value)),
      erlang:convert_time_unit(Part, microsecond, millisecond)
  end,
  erlang:send_after(WatchdogMs, self(), watchdog),
  ?LOG_INFO("watchdog ~p", [WatchdogMs]),
  {ok, WatchdogMs}.


handle_info(watchdog, none) ->
  {noreply, none};
handle_info(watchdog, WatchdogMs) ->
  watchdog(),
  erlang:send_after(WatchdogMs, self(), watchdog),
  {noreply, WatchdogMs}.


terminate(_,_) -> ok.

@weiss
Copy link

weiss commented Jul 9, 2020

FWIW, I created something similar, except my version keeps the socket in the gen_server state rather than reopening it on each notification (and uses the normal gen_server timeout for triggering watchdog notifications).

Maybe we should publish this as a proper library application …

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment