0

I might have the concept/language all wrong here so feel free to point me in the right direction if my thinking is wrong.

I use a home media streaming app that is not really supported any longer, called StreamBaby. It's designed to stream over the LAN to older TiVo DVRs and I love it, so still use it daily. It's java, and when executed it opens up a Terminal (I'm running it on macOS) and runs a simple script, which then launches Java and I guess does whatever it's doing.

Unfortunately the application crashes (and I see the exception in Terminal) just about every day at some point, and I have to close it from Terminal and launch it again. No pattern to these crashes, it just has some bugs I guess. What I would like to happen, is if it crashes then the script just terminates and re-launches, since that's all I'm doing with it manually anyway. Main reason is that my wife watches her shows via this regularly and when it crashes, she has to ask me to "fix it" and I'm not always available to do so. Would love to have it just restart.

Is there a way to do this in terminal from the batch script it includes? That script is:

@echo off
set LAUNCHDIR="%CD%"
pushd "%~dp0\native"
java -Djava.net.preferIPv4Stack=true -Xmx256m -Xmx256m -jar "%~dp0/jbin/streambaby.jar" %1 %2 %3 %4 %5 %6 %7 %8 
echo Exited.
pause
popd

Or would this require the java application itself to have this exception handling?

EDIT: Here is an example of the error when it crashes:

WARNING: run() exception 
java.io.IOException: No route to host (sendto failed)
    at java.net.PlainDatagramSocketImpl.send(Native Method)
    at java.net.DatagramSocket.send(DatagramSocket.java:693)
    at javax.jmdns.impl.JmDNSImpl.send(JmDNSImpl.java:1130)
    at javax.jmdns.impl.tasks.Responder.run(Responder.java:279)
    at java.util.TimerThread.mainLoop(Timer.java:555)
    at java.util.TimerThread.run(Timer.java:505)

I don't know that it's always identical, but it is always a run() exception.

1

2 Answers 2

1

The simplest solution that every scripting language would support is a loop. Simply repeat the java call forever:

@echo off
set LAUNCHDIR="%CD%"
pushd "%~dp0\native"
:start
java -Djava.net.preferIPv4Stack=true -Xmx256m -Xmx256m -jar "%~dp0/jbin/streambaby.jar" %1 %2 %3 %4 %5 %6 %7 %8 
echo Exited.
goto start
pause
popd

A more sophisticated solution could involve running the application as a service. The service manager can simply restart the service if it crashes. On Linux this is relatively easy, but on Windows a program must conform to a special interface. Luckily, there’s NSSM, a service wrapper tool. It could be used to run this Java application as a Windows service.

Of course, if you are satisfied with manually launching the media server and having a console window around, that’s also fine.

6
  • Well unfortunately as I expected, this didn't work. It still crashed with a run exception and then just hung as usual, and I had to manually kill it and restart.
    – JVC
    Commented Mar 1, 2022 at 1:08
  • Sorry, by "this" I mean the loop solution.
    – JVC
    Commented Mar 1, 2022 at 4:01
  • Too bad. Maybe the log could be parsed with expect.
    – Daniel B
    Commented Mar 1, 2022 at 14:08
  • I'm afraid I don't know what "the log" is in this context.
    – JVC
    Commented Mar 2, 2022 at 17:46
  • It’s what Streambaby is printing in the Terminal window. Where you see the exception message. But maybe it’s good enough to just restart every day at midnight or something?
    – Daniel B
    Commented Mar 2, 2022 at 18:43
0

There are multiple possible methods:

Method 1:

@DanielB does give the simplest answer. Put a loop around your script.

Method 2:

The more elegant would be to make a launchd .plist file and put it in (not link it in) /Library/LaunchDaemons.

An elementary .plist would look like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN
http://www.apple.com/DTDs/PropertyList-1.0.dtd >
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.jvc.streambaby</string>
    <key>ProgramArguments</key>
    <array>
         <string>/path/to/your/script</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>StandardErrorPath</key>
    <string>/dev/null</string>
    <key>StandardOutPath</key>
    <string>/dev/null</string>
</dict>
</plist>

You can load up the plist with sudo launchctl load -w /Library/LaunchDaemons/com.jvc.streambaby.plist if you have named your .plist file as com.jvc.streambaby.plist, of course!

Note: Please change /dev/null to some real file path if you need to save the error and output streams as logs.

Method 3:

The most disguisting and visual method would be to use Automator to execute your script. There must be a way to restart your script somewhere in there. But who in their right mind uses a GUI when you can script anyway!

There may be other methods, but I am not an Apple guy anyway so don't know.

Method 4:

Here is a small script:

Be sure to change the variables, especially the streambaby root directory and run it with python3 (made with Python 3.9.x on linux).

#!/usr/bin/env python3


# set the path to the streambaby directory
# do not add a trailing slash (/)
streambaby_home = "/home/<username>/streambaby"


import subprocess
import signal
import sys
import os

from pathlib import Path


# variable to keep track if the app should restart
# True by default
should_run = True

# everything should be made visible immediately
os.environ["PYTHONUNBUFFERED"] = "1"

# copy the current environment
proc_env = os.environ.copy()

# is this really necessary, but was in the batch file so....
proc_env["LAUNCHDIR"] = str(Path.home())

# split the whole command into a list

exec_cmd = [
    'java',
    '-Djava.net.preferIPv4Stack=true',
    '-Xmx256m', # don't know why this argument is added twice
    '-Xmx256m',
    '-jar',
    '{0}/jbin/streambaby.jar'.format(streambaby_home)
    ]

# put the subprocess execution in a function for cleanliness
# return the process handle
def runapp(cmd, env):
    return subprocess.Popen(
        cmd,                        # the command to execute
        env=env,                    # setup the environment variables
        shell=False,                # not using a shell, change to True if doesn't work
        stdout = subprocess.PIPE,   # pipe the console output so we can read it
        stderr = subprocess.STDOUT, # somethings pop up on stderr, we send them to stdout anyway
        start_new_session=True      # using a session makes the whole set of processes easier to kill
                                    # works only on *nix type systems
        )

# now we start the program in a loop and poll() the hell out of it
while should_run:
    proc = runapp(exec_cmd, proc_env)
    while True:
        stdout_string = str(proc.stdout.readline())
        if proc.poll() is not None: # poll returns None while the process is executing
            break
        if stdout_string != '':
            print(stdout_string) # if someone wants to see the output anyway
            if "run() exception" in stdout_string.strip(): # only checking for those 2 words
                # we have hit a snag; kill it
                # ...(evil laugh)...
                # SIGKILL is my favorite type of signal
                # sounds badass
                os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
            elif " Could not create the Java Virtual Machine" in stdout_string:
                # if the Java VM fails to start, there is no point in trying anymore
                should_run = False

    # get the return code
    return_code = proc.poll()

    print("The process exited with exit_code ( {0} )".format(return_code))

    # a normal process which terminates properly should return 0
    # I think we may need to change this
    # don't know what streambaby should be returning
    if return_code == 0 and should_run: 
        # if the process exits normally, don't restart the process
        should_run = False
13
  • Thanks, the daemon idea is over my head but I'll try a simple loop and see if that works. My concern is that the application seems to hang after the exception, so I'm not convinced that a loop will ever detect that it needs to loop. Will find out!
    – JVC
    Commented Feb 27, 2022 at 19:15
  • You might wanna listen for the exception on the console (little bit of programming required). E. g. Start a subprocess with Python and regex the output for the exact exception string. Kill the process when you hit it and respawn. Commented Feb 28, 2022 at 3:56
  • Thanks, unfortunately that's far beyond my abilities.
    – JVC
    Commented Feb 28, 2022 at 3:56
  • 1
    busy right now, but will post the script when I get time Commented Feb 28, 2022 at 3:59
  • 1
    @JVC, put the script in a file somewhere, change the streambaby_home variable to the directory where streambaby resides; open a terminal in the directory where you saved the file and run python3 <script name>.py Commented Mar 3, 2022 at 7:00

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .