Restarting JUCE app as standalone with childProcess.start()

Hi,
I checked the different ways to restart a JUCE app on the forum and stumble uppon different discussions:

I also went from the “request from the outside to do the restart” route as well via an apple script or external app.

I faced permissions issues, it’s very feasable but requires codesigned app etc

So I did the following restart function called from the component window destructor with an alertWindow async, it’s ok but the “childProcess.start()” function never works and falls back to “child.startAsProcess()” which is not as good since it’s restarting the app from the terminal which shows up on the user’s screen and if the terminal is closed the JUCE app is shut down as well.

I added xcode loggers at each steps to track down the culprit and even if child.start() is not working it’s returning (1 | True) .

i checked if the app can go multi instance as well, it returns true, so not the problem apparently.

Any idea to make it work properly with “childProcess.start()” and not “startAsProcess()” ?

Here is my script :

CtrlrSettings::~CtrlrSettings()
{
    deleteAndZero (propertyPanel);
    
    if (JUCEApplication::isStandaloneApp())
    {
    // Show Ok/Cancel dialog to confirm restart
    const auto callbackRestart = juce::ModalCallbackFunction::create([this] (int resultBox){
        if (resultBox == 0){nullptr;} // for Cancel
        if (resultBox == 1){restart();} // for OK
    });
    juce::NativeMessageBox::showOkCancelBox(juce::AlertWindow::QuestionIcon,"CtrlrX", "Restarting CtrlrX is required to apply new settings.", nullptr, callbackRestart);
    }
    else{
        // For VST/AU instances
        AlertWindow::showMessageBox (AlertWindow::WarningIcon, "CtrlrX", "Restart to apply new settings."); // Added v5.6.31
    }
}


void CtrlrSettings::restart()
{
    // Check if multiple instances are allowed
    auto runningInstance = JUCEApplication::getInstance();
    bool multiInstance = runningInstance->JUCEApplication::moreThanOneInstanceAllowed();
        
    if (multiInstance) {
        Logger::writeToLog("Multiple instances allowed.");
    }
    else{
        Logger::writeToLog("Multiple instances not allowed.");
    }
    
    String executablePath = File::getSpecialLocation(File::currentExecutableFile).getFullPathName();
    File exeFile = File(executablePath);
    
    if (exeFile.exists())
    {
        Logger::writeToLog("Executable file found");
        
        int result = 1; // Assume failure initially
        
        auto osType = SystemStats::getOperatingSystemType();
        Logger::writeToLog("OS type: " + String(osType));
        
        String osName = SystemStats::getOperatingSystemName();
        Logger::writeToLog("OS Name: " + String(osName));
        
        if ((SystemStats::getOperatingSystemType() & juce::SystemStats::MacOSX) != 0) {  // To test whether any version of OSX is running
            // For OSX & macOS
            Logger::writeToLog("Launching on macOS using ChildProcess.");
            ChildProcess childProcess;
            
            
            // Attempt to start the process with ChildProcess
            if (childProcess.start(exeFile.getFullPathName())) {
                Logger::writeToLog("ChildProcess start() successful."); // This is not properly working
                result = 0;
            } else {
                Logger::writeToLog("ChildProcess start() failed.");

                // If ChildProcess fails, try startAsProcess()
                Logger::writeToLog("Trying startAsProcess().");
                result = exeFile.startAsProcess();
                if (result == 0) {
                    Logger::writeToLog("startAsProcess() successful.");
                } else {
                    Logger::writeToLog("startAsProcess() failed.");
                }
            }
        }
        
        else if (SystemStats::getOperatingSystemType() == SystemStats::Windows){
            // For Windows
            Logger::writeToLog("Launching on Windows using exeFile.startAsProcess().");
            result = exeFile.startAsProcess();
        }
        
        else{
            // Handle unsupported operating systems
            Logger::writeToLog("Launching on other operating system.");
            std::cerr << "Launching on other operating system." << std::endl;
            result = 1;
        }
                
        if (result == 1){
            // Handle error (e.g., log error message)
            std::cerr << "Error launching executable: " << result << std::endl;
            Logger::writeToLog("Error launching executable: " + String(result));
        }
        else
        {
            Logger::writeToLog("Executable launched successfully.");
        }
    }
    else{
        // Handle error: executable file not found
        std::cerr << "Executable file not found." << std::endl;
        Logger::writeToLog("Executable file not found.");
    }
        
    Logger::writeToLog("Sending quit request to current application.");
    JUCEApplication::getInstance()->systemRequestedQuit();
    Logger::writeToLog("Quit request sent. Waiting for 500ms.");
    Thread::sleep(500);
}

And this is my Xcode log :

Executable file found
OS type: 270
OS Name: Mac OSX 10.14.6
Launching on macOS using ChildProcess.
ChildProcess start() successful.
Executable launched successfully.
Sending quit request to current application.
Quit request sent. Waiting for 500ms.

Thanks for your help and happy JUCY year for 2025 to everyone :wink:

Damien

The way I’ve usually done this is via a “Helper application” which does the restart process once the main app (the one being updated) exited:

#include <iostream>
#include <thread>
#include <chrono>
#include <cstdlib>
#include <juce_core/juce_core.h>

int main(int argc, char* argv[])
{
    if (argc < 2)
    {
        std::cerr << "Usage: RestartHelper <path_to_updated_app>" << std::endl;
        return 1;
    }

    juce::String appPath = argv[1];

    // Check if the appPath exists
    if (!juce::File(appPath).exists())
    {
        std::cerr << "Error: Application path does not exist: " << appPath << std::endl;
        return 1;
    }

    // Wait for the original process to terminate
    std::cout << "Waiting for the original application to exit..." << std::endl;
    bool originalAppRunning = true;
    while (originalAppRunning)
    {
        originalAppRunning = false;
        auto processes = juce::Process::findProcesses();
        for (const auto& process : processes)
        {
            if (process.processName.containsIgnoreCase("YourAppName")) // Replace with your app name
            {
                originalAppRunning = true;
                break;
            }
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }

    // Launch the updated application
    std::cout << "Launching updated application..." << std::endl;
    juce::String launchCommand = appPath + "/Contents/MacOS/YourAppExecutable"; // Replace with your app's executable
    bool success = juce::Process::openDocument(launchCommand, {});

    if (!success)
    {
        std::cerr << "Failed to launch the updated application." << std::endl;
        return 1;
    }

    std::cout << "Updated application launched successfully!" << std::endl;
    return 0;
}

In your Main application, fire up this Helper app after the update process has completed, with something like this:


void launchHelperAndExit(const juce::File& helperPath, const juce::File& updatedAppPath)
{
    if (!helperPath.exists() || !updatedAppPath.exists())
    {
        juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::WarningIcon,
                                               "Error",
                                               "Helper or updated app not found.");
        return;
    }

    juce::ChildProcess helperProcess;
    juce::StringArray args;
    args.add(helperPath.getFullPathName());
    args.add(updatedAppPath.getFullPathName());

    if (!helperProcess.start(args))
    {
        juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::WarningIcon,
                                               "Error",
                                               "Failed to launch restart helper.");
        return;
    }

    juce::JUCEApplication::getInstance()->systemRequestedQuit();
}

Put the HelperApp in your MainApps Resources/ folder (within the app bundle) and don’t forget that this will also need to be codesigned and notarized …

1 Like

@ibisum thanks for your help.
I will definitely go this way if I cannot get childProcess.start() working properly.
I did the exact same thing to test on mac but with an applescript exported as executable to work as a tier “helper” app to restart from outside the main app.
It didn’t work because codesigning was required and the process was blocked, like any malware gatekeeper would do.

I read on the forum childprocess.start() had some problems to provide the proper bool flag back if the launched app was not actually started and pass True even if it’s false. It was a problem for me to fall back on the startAsProcess() function since it’s returning OK.

If I could just do childprocess.start() of the main app to open it twice then close the first instance that would be so much easier in my case.

Happy new year and again, thanks for your help!

Damien

Keep in mind that your technique isn’t actually restarting the process - its creating two copies of the same process, and there is a chance that you won’t get the new executable in the second instance. The update process really necessitates having a ‘clean break’ - i.e. HelperApp could, itself, be the thing that actually does the update while MainApp is completely gone from the system.

Having a HelperApp also means you can do things like housekeeping your apps “~/Library/Application Support” folder if you need to, or use it to convert presets to a new format if you ever do that.

Just sayin’ …

1 Like

This is exactly what’s happing right now with childProcess.start(), the second process is not starting (unless started with startAsProcess) and the first one is shut down. big fail here.
I’ll try your method next week and will let you know.
Thanks for the tips!