Skip to content

Commit 6034bfc

Browse files
committed
Boot - create comms between Daemon and Tau
This enables Tauto send the system pid of the erlang beam process over a TCP connection to Daemon now that the Elixir release scripts boots things indirectly. The comms channel consists of a single TCP connection which expects OSC in a similar manner to SuperCollider - 32 bit unsigned big endian data size followed by that many bytes of OSC message. This new TCP socket now also acts as the mechanism for the Daemon to determine whether or not Tau is still alive.
1 parent 584fa53 commit 6034bfc

File tree

8 files changed

+157
-46
lines changed

8 files changed

+157
-46
lines changed

app/server/erlang/tau/boot-lin.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ cd ${SCRIPT_DIR}
66

77
echo "Booting Sonic Pi on Linux..."
88

9-
TAU_ENABLED=$1 TAU_INTERNAL=$2 TAU_MIDI_ENABLED=$3 TAU_LINK_ENABLED=$4 TAU_IN_PORT=$5 TAU_API_PORT=$6 TAU_SPIDER_PORT=$7 exec _build/"${MIX_ENV:-dev}"/rel/tau/bin/tau start
9+
TAU_ENABLED=$1 TAU_INTERNAL=$2 TAU_MIDI_ENABLED=$3 TAU_LINK_ENABLED=$4 TAU_IN_PORT=$5 TAU_API_PORT=$6 TAU_SPIDER_PORT=$7 TAU_DAEMON_PORT=$8 _build/"${MIX_ENV:-dev}"/rel/tau/bin/tau start

app/server/erlang/tau/boot-mac.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ cd ${SCRIPT_DIR}
66

77
echo "Booting Sonic Pi on macOS..."
88

9-
TAU_ENABLED=$1 TAU_INTERNAL=$2 TAU_MIDI_ENABLED=$3 TAU_LINK_ENABLED=$4 TAU_IN_PORT=$5 TAU_API_PORT=$6 TAU_SPIDER_PORT=$7 exec _build/"${MIX_ENV:-dev}"/rel/tau/bin/tau start
9+
TAU_ENABLED=$1 TAU_INTERNAL=$2 TAU_MIDI_ENABLED=$3 TAU_LINK_ENABLED=$4 TAU_IN_PORT=$5 TAU_API_PORT=$6 TAU_SPIDER_PORT=$7 TAU_DAEMON_PORT=$8 _build/"${MIX_ENV:-dev}"/rel/tau/bin/tau start

app/server/erlang/tau/boot-win.bat

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ set TAU_LINK_ENABLED=%4%
1212
set TAU_IN_PORT=%5%
1313
set TAU_API_PORT=%6%
1414
set TAU_SPIDER_PORT=%7%
15+
set TAU_DAEMON_PORT=%8%
1516

1617
IF NOT DEFINED MIX_ENV SET "MIX_ENV=dev"
1718

app/server/erlang/tau/lib/tau.ex

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,49 +4,45 @@ use Application
44
@impl true
55
def start(_type, _args) do
66
IO.puts "All systems booting.,.."
7-
8-
enabled = extract_env("TAU_ENABLED", :bool)
9-
internal = extract_env("TAU_INTERNAL", :bool)
10-
midi_enabled = extract_env("TAU_MIDI_ENABLED", :bool)
11-
link_enabled = extract_env("TAU_LINK_ENABLED", :bool)
12-
in_port = extract_env("TAU_IN_PORT", :int)
13-
api_port = extract_env("TAU_API_PORT", :int)
14-
spider_port = extract_env("TAU_SPIDER_PORT", :int)
15-
16-
Application.put_env(:tau, :enabled, enabled)
17-
Application.put_env(:tau, :internal, internal)
18-
Application.put_env(:tau, :midi_enabled, midi_enabled)
19-
Application.put_env(:tau, :link_enabled, link_enabled)
20-
Application.put_env(:tau, :in_port, in_port)
21-
Application.put_env(:tau, :api_port, api_port)
22-
Application.put_env(:tau, :spider_port, spider_port)
7+
IO.puts "Pid: #{System.pid()}"
8+
enabled = extract_env("TAU_ENABLED", :bool, true)
9+
internal = extract_env("TAU_INTERNAL", :bool, true)
10+
midi_enabled = extract_env("TAU_MIDI_ENABLED", :bool, true)
11+
link_enabled = extract_env("TAU_LINK_ENABLED", :bool, true)
12+
in_port = extract_env("TAU_IN_PORT", :int, 5000)
13+
api_port = extract_env("TAU_API_PORT", :int, 5001)
14+
spider_port = extract_env("TAU_SPIDER_PORT", :int, 5002)
15+
daemon_port = extract_env("TAU_DAEMON_PORT", :int, -1)
2316

2417
:tau_server_sup.set_application_env(enabled,
2518
internal,
2619
midi_enabled,
2720
link_enabled,
2821
in_port,
2922
api_port,
30-
spider_port)
23+
spider_port,
24+
daemon_port
25+
)
3126

3227
# Although we don't use the supervisor name below directly,
3328
# it can be useful when debugging or introspecting the system.
3429
:tau_server_sup.start_link()
3530
end
3631

37-
def extract_env(name, kind) do
32+
def extract_env(name, kind, default) do
3833
env_val = System.get_env(name)
39-
if !env_val do
40-
raise "Error, missing ENV Variable #{name}"
34+
res = if !env_val do
35+
IO.puts "No env variable supplied for #{name} using default: #{default}"
36+
default
37+
else
38+
extracted = case kind do
39+
:bool -> extract_env_bool(env_val)
40+
:int -> extract_env_int(env_val)
41+
:string -> env_val
42+
end
43+
IO.puts "extracting env #{name} #{kind} #{extracted}"
44+
extracted
4145
end
42-
res = case kind do
43-
:bool -> extract_env_bool(env_val)
44-
:int -> extract_env_int(env_val)
45-
:string -> env_val
46-
47-
end
48-
49-
IO.puts "extracting env #{name} #{kind} #{res}"
5046
res
5147
end
5248

app/server/erlang/tau/src/tau_server/tau_server_api.erl

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ start_link(CueServer, MIDIServer, LinkServer) ->
7878
init(Parent, CueServer, MIDIServer, LinkServer) ->
7979
register(?SERVER, self()),
8080
APIPort = application:get_env(?APPLICATION, api_port, undefined),
81+
DaemonPort = application:get_env(?APPLICATION, daemon_port, undefined),
82+
8183
io:format("~n"
8284
"+--------------------------------------+~n"
8385
" This is the Sonic Pi API Server ~n"
@@ -88,10 +90,24 @@ init(Parent, CueServer, MIDIServer, LinkServer) ->
8890
[erlang:system_info(otp_release), APIPort]),
8991

9092
{ok, APISocket} = gen_udp:open(APIPort, [binary, {ip, loopback}]),
91-
93+
io:format("connecting to Daemon via TCP...~n", []),
94+
{ok, DaemonSocket} = gen_tcp:connect({127,0,0,1}, DaemonPort, [
95+
binary,
96+
{active, true},
97+
{packet, 4},
98+
{keepalive, true}
99+
]),
100+
101+
PidMsg = osc:encode(["/tau_pid", os:getpid()]),
102+
io:format("Sending Pid to Daemon...~n", []),
103+
104+
gen_tcp:send(DaemonSocket, PidMsg),
92105
%% tell parent we have allocated resources and are up and running
93106
proc_lib:init_ack(Parent, {ok, self()}),
94107

108+
109+
110+
95111
debug(2, "listening for API commands on socket: ~p~n",
96112
[try erlang:port_info(APISocket) catch _:_ -> undefined end]),
97113
State = #{parent => Parent,
@@ -106,6 +122,10 @@ init(Parent, CueServer, MIDIServer, LinkServer) ->
106122

107123
loop(State) ->
108124
receive
125+
{tcp, Socket, Data} ->
126+
debug(3, "api server got TCP on ~p:~p~n", [Socket, Data]),
127+
?MODULE:loop(State);
128+
109129
{timeout, Timer, {call, Server, Msg, Tracker}} ->
110130
Server ! Msg,
111131
tau_server_tracker:forget(Timer, Tracker),

app/server/erlang/tau/src/tau_server/tau_server_sup.erl

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99

1010
%% Supervisor callbacks
1111
-export([init/1]).
12-
-export([set_application_env/7]).
13-
12+
-export([set_application_env/8]).
1413

1514
-define(APPLICATION, tau).
1615

@@ -39,15 +38,17 @@ set_application_env(Enabled,
3938
LinkEnabled,
4039
InPort,
4140
ApiPort,
42-
SpiderPort) ->
41+
SpiderPort,
42+
DaemonPort) ->
4343

4444
application:set_env(?APPLICATION, enabled, Enabled),
4545
application:set_env(?APPLICATION, internal, Internal),
4646
application:set_env(?APPLICATION, midi_enabled, MidiEnabled),
4747
application:set_env(?APPLICATION, link_enabled, LinkEnabled),
4848
application:set_env(?APPLICATION, in_port, InPort),
4949
application:set_env(?APPLICATION, api_port, ApiPort),
50-
application:set_env(?APPLICATION, spider_port, SpiderPort).
50+
application:set_env(?APPLICATION, spider_port, SpiderPort),
51+
application:set_env(?APPLICATION, daemon_port, DaemonPort).
5152

5253
init(_Args) ->
5354
CueServer = tau_server_cue:server_name(),

app/server/ruby/bin/daemon.rb

Lines changed: 103 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
require 'fileutils'
1919
require 'time'
2020

21+
require_relative "../lib/sonicpi/osc/osc"
22+
require_relative "../lib/sonicpi/promise"
23+
2124
# Make sure vendored tomlrb lib is on the Ruby path so it can be required
2225
Dir["#{File.expand_path("../../vendor", __FILE__)}/*/lib/"].each do |vendor_lib|
2326
$:.unshift vendor_lib
@@ -148,7 +151,15 @@ def initialize
148151
@scsynth_booter = ScsynthBooter.new(ports)
149152

150153
Util.log "Booting Tau"
151-
@tau_booter = TauBooter.new(ports)
154+
begin
155+
@tau_booter = TauBooter.new(ports)
156+
rescue StandardError => e
157+
Util.log "Oh no, something went wrong booting Tau"
158+
Util.log "Error Class: #{e.class}"
159+
Util.log "Error Message: #{e.message}"
160+
Util.log "Error Backtrace: #{e.backtrace.inspect}"
161+
end
162+
152163

153164
Util.log "Booting Spider Server"
154165
@spider_booter = SpiderBooter.new(ports)
@@ -193,7 +204,7 @@ def spawn_zombie_kill_switch(port_num, &blk)
193204
Util.log "Waiting for GUI on port #{port_num}...."
194205

195206
unless IO.select([keep_alive_server], nil, nil, connect_timeout)
196-
Util.log "Error. Unable to connect to GUI process on TCP port #{port_num}"
207+
Util.log "Critical Error. Unable to connect to GUI process on TCP port #{port_num}"
197208
@safe_exit.exit
198209
end
199210

@@ -215,9 +226,10 @@ def spawn_zombie_kill_switch(port_num, &blk)
215226
Util.log "Error Backtrace: #{e.backtrace.inspect}"
216227
end
217228

218-
Util.log "Shutting down..."
229+
Util.log "Critical Error. Lost comms with GUI."
219230
client.close if client
220231
keep_alive_server.close if keep_alive_server
232+
221233
@safe_exit.exit
222234
end
223235
end
@@ -371,16 +383,23 @@ def idempotent_exit_cleanup
371383
end
372384
end
373385

374-
375-
376386
class ProcessBooter
377387
attr_reader :pid, :args, :cmd
378388
def initialize(cmd, args, log_path)
389+
@pid = nil
379390
@args = args.map {|el| el.to_s}
380391
@cmd = cmd
381392
@log_file = File.open(log_path, 'a')
382393
raise "Unable to create log file at path: #{log_path}" unless @log_file
383-
boot
394+
begin
395+
boot
396+
rescue StandardError => e
397+
Util.log "Error: something went wrong booting process: #{cmd}, #{args}, #{log_path}"
398+
Util.log "Error Class: #{e.class}"
399+
Util.log "Error Message: #{e.message}"
400+
Util.log "Error Backtrace: #{e.backtrace.inspect}"
401+
@log_file.close if @log_file
402+
end
384403
end
385404

386405
def inspect
@@ -499,21 +518,72 @@ def initialize(ports)
499518

500519
class TauBooter < ProcessBooter
501520
def initialize(ports)
521+
@tau_pid = Promise.new
502522
enabled = true
503523
internal = false
504524
midi_enabled = true
505525
link_enabled = true
506526
in_port = ports["osc-cues"]
507527
api_port = ports["tau"]
508528
spider_port = ports["listen-to-tau"]
529+
daemon_port = ports["daemon-listen-to-tau"]
530+
531+
tau_comms = TCPServer.new "127.0.0.1", daemon_port
532+
osc_decoder = SonicPi::OSC::OscDecode.new
533+
534+
Thread.new do
535+
client = nil
536+
537+
@tau_comms_thread = Thread.new do
538+
Util.log "-----> Accepting incoming connection from Tau"
539+
client = tau_comms.accept # Wait for a client to connect
540+
Util.log "-----> Connection accepted"
541+
542+
recv_osc = lambda do
543+
size_str = client.recvfrom(4, Socket::MSG_WAITALL)[0].chomp
544+
size = size_str.unpack('N')[0]
545+
raise "Critical Error: Bad TCP OSC size received from Tau #{size_str}" unless size
546+
data_raw = client.recvfrom(size, Socket::MSG_WAITALL)[0].chomp
547+
osc_decoder.decode_single_message(data_raw)
548+
end
549+
550+
551+
begin
552+
data = recv_osc.call
553+
tau_pid = Integer(data[1][0])
554+
@tau_pid.deliver!(tau_pid)
555+
Util.log "-----> Tau Pid: #{tau_pid}"
556+
557+
loop do
558+
data = recv_osc.call
559+
Util.log "Received OSC from Tau: #{data}"
560+
# In the future this is where we can handle any callbacks from Tau
561+
end
562+
rescue Errno::ECONNRESET
563+
Util.log "Tau closed TCP connection"
564+
rescue StandardError => e
565+
Util.log "Critical Error, whilst communicating with Tau:"
566+
Util.log "Error Class: #{e.class}"
567+
Util.log "Error Message: #{e.message}"
568+
Util.log "Error Backtrace: #{e.backtrace.inspect}"
569+
end
570+
end
571+
572+
@tau_comms_thread.join
573+
client.close if client
574+
tau_comms.close if tau_comms
575+
end
509576

510-
args = [enabled,
577+
args = [
578+
enabled,
511579
internal,
512580
midi_enabled,
513581
link_enabled,
514582
in_port,
515583
api_port,
516-
spider_port]
584+
spider_port,
585+
daemon_port
586+
]
517587

518588
if Util.os == :windows
519589
cmd = Paths.mix_release_boot_path
@@ -524,6 +594,20 @@ def initialize(ports)
524594

525595
super(cmd, args, Paths.tau_log_path)
526596
end
597+
598+
def process_running?
599+
@tau_comms_thread.alive?
600+
end
601+
602+
def kill
603+
@tau_comms_thread.kill
604+
@pid = @tau_pid.get
605+
super
606+
end
607+
608+
def wait
609+
@tau_comms_thread.join
610+
end
527611
end
528612

529613

@@ -749,6 +833,8 @@ class PortDiscovery
749833
# Port which the Ruby server listens to messages back from the Tau server
750834
"listen-to-tau" => :dynamic,
751835

836+
"daemon-listen-to-tau" => :dynamic,
837+
752838
# Port which the server uses to communicate via websockets
753839
# (This is currently unused.)
754840
"websocket" => :dynamic
@@ -777,6 +863,7 @@ def initialize(safe_exit)
777863
"osc-cues",
778864
"tau",
779865
"listen-to-tau",
866+
"daemon-listen-to-tau",
780867
"websocket"].inject({}) do |res, port_name|
781868

782869
default = nil
@@ -788,15 +875,19 @@ def initialize(safe_exit)
788875
elsif default == :paired
789876
port = res[port_name[1]]
790877
else
791-
port = default
878+
if check_port(default)
879+
port = default
880+
else
881+
port = find_free_port
882+
end
792883
end
793884
res[port_name[0]] = port.to_i
794885
else
795886
default = PORT_CONFIG[port_name]
796887
if default == :dynamic
797888
port = find_free_port
798889
elsif default == :paired
799-
raise "Invalid port default for port: #{port_name}. This port can not be paired."
890+
Util.log "Critical error: Invalid port default for port: #{port_name}. This port can not be paired."
800891
@safe_exit.exit
801892
else
802893
port = default
@@ -816,6 +907,7 @@ def check_port(port)
816907
begin
817908
socket = UDPSocket.new
818909
socket.bind('127.0.0.1', port)
910+
Util.log "checked port #{port}, #{socket}"
819911
socket.close
820912
available = true
821913
rescue StandardError
@@ -827,6 +919,7 @@ def check_port(port)
827919
def find_free_port
828920
while !check_port(@last_free_port += 1)
829921
if @last_free_port > 65535
922+
Util.log "Critical error: Unable to find a free port."
830923
@safe_exit.exit
831924
end
832925
end

0 commit comments

Comments
 (0)