1

I'm trying to use windows-service to run an actix web app. It provides a nice API and mostly works. I can start my service just fine. However, when I try to stop my service, I get the following error: Error 109: The pipe has been ended (it does stop the service however).

I'm mostly just using the example provided for windows-service, but here is the relevant code (for context and all the wrapper functions, check out https://github.com/mullvad/windows-service-rs/blob/master/examples/ping_service.rs):

pub fn run_service() -> Result<()> {

    fn hi() -> impl actix_web::Responder {
        "Hello!\r\n"
    }

    let sys = actix_rt::System::new("test");

    actix_web::HttpServer::new(move || {
        actix_web::App::new()
            .route("/", actix_web::web::get().to(hi))
    })
    .bind("0.0.0.0:3000").unwrap()
    .start();

    let event_handler = move |control_event| -> ServiceControlHandlerResult {
        match control_event {
            ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
            ServiceControl::Stop => {
                actix_rt::System::with_current(|s| s.stop());
                ServiceControlHandlerResult::NoError
            }
            _ => ServiceControlHandlerResult::NotImplemented,
        }
    };

    let status_handle = service_control_handler::register(SERVICE_NAME, event_handler)?;

    status_handle.set_service_status(ServiceStatus {
        service_type: SERVICE_TYPE,
        current_state: ServiceState::Running,
        controls_accepted: ServiceControlAccept::STOP,
        exit_code: ServiceExitCode::Win32(0),
        checkpoint: 0,
        wait_hint: Duration::default(),
    })?;

    sys.run().unwrap();

    status_handle.set_service_status(ServiceStatus {
        service_type: SERVICE_TYPE,
        current_state: ServiceState::Stopped,
        controls_accepted: ServiceControlAccept::empty(),
        exit_code: ServiceExitCode::Win32(0),
        checkpoint: 0,
        wait_hint: Duration::default(),
    })?;

    Ok(())
}

If I put the System::stop in a thread::spawn, I get a different error: The service did not return an error. This could be an internal Windows error or an internal service error. In this case it does not stop the service.

I've put in some logging, and it doesn't look like the code ever gets past the sys.run().unwrap(), which is strange.

Any thoughts? I've never used the Windows Service API before so I don't really know what I'm doing.

EDIT

I figured out what the main issue is: I have to notify Windows the service has stopped before stopping the service. I put together a clunky way to make it work:

std::thread::spawn(move || {
    loop {
        if shutdown_signal.load(Ordering::Relaxed) {

            status_handle.set_service_status(ServiceStatus {
                service_type: SERVICE_TYPE,
                current_state: ServiceState::Stopped,
                controls_accepted: ServiceControlAccept::empty(),
                exit_code: ServiceExitCode::Win32(0),
                checkpoint: 0,
                wait_hint: Duration::default(),
            }).unwrap();

            actix_rt::System::current().stop();
            break;
        }
    }
});
sys.run().unwrap();
// ...

where shutdown_signal is an AtomicBool I set to true in the event handler. I'm going to see if I can do this instead somehow through actix_rt.

Jacob Brown
  • 7,221
  • 4
  • 30
  • 50

1 Answers1

2

Answering my own question. I think this is the best way to handle it, though I would be happy to see other solutions!

pub fn run_service() -> Result<()> {

    use futures::Future;

    fn hi() -> impl actix_web::Responder {
        "Hello!\r\n"
    }

    let sys = actix_rt::System::new("test");

    actix_web::HttpServer::new(move || {
        actix_web::App::new()
            .route("/", actix_web::web::get().to(hi))
    })
    .bind("0.0.0.0:3000").unwrap()
    .start();

    let (mut send_stop, recv_stop) = {
        let (p, c) = futures::sync::oneshot::channel::<()>();
        (Some(p), c)
    };

    let event_handler = move |control_event| -> ServiceControlHandlerResult {
        match control_event {
            ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
            ServiceControl::Stop => {
                send_stop.take().unwrap().send(()).unwrap();
                ServiceControlHandlerResult::NoError
            }
            _ => ServiceControlHandlerResult::NotImplemented,
        }
    };

    let status_handle = service_control_handler::register(SERVICE_NAME, event_handler)?;

    status_handle.set_service_status(ServiceStatus {
        service_type: SERVICE_TYPE,
        current_state: ServiceState::Running,
        controls_accepted: ServiceControlAccept::STOP,
        exit_code: ServiceExitCode::Win32(0),
        checkpoint: 0,
        wait_hint: Duration::default(),
    })?;

    actix_rt::spawn(recv_stop.map(move |_| {
        status_handle.set_service_status(ServiceStatus {
            service_type: SERVICE_TYPE,
            current_state: ServiceState::Stopped,
            controls_accepted: ServiceControlAccept::empty(),
            exit_code: ServiceExitCode::Win32(0),
            checkpoint: 0,
            wait_hint: Duration::default(),
        }).unwrap();

        actix_rt::System::current().stop()
    }).map_err(|_| ()));

    sys.run().unwrap();

    Ok(())
}
Jacob Brown
  • 7,221
  • 4
  • 30
  • 50