-
Notifications
You must be signed in to change notification settings - Fork 84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Resource leak if a service spontaneously stops #81
Comments
That's a valid point. I re-read the documentation for
Which hints that it should be safe to release So you reckon that the race between Sorry If I have to ask twice, because it seems like a fundamental flaw in windows services API if that's the case. |
Yes, I read that paragraph too. However, the problem is that the control handler might already have been called by the time the ServiceMain thread calls What I would hope would happen, is that the Windows API has something like a recursive mutex between
Yes, I know it’s surprising, and AFAICT it is a fundamental flaw in the API. I’ve experimented a bit and can’t figure out a way around it. Of course there’s no problem if the service doesn’t spontaneously terminate (i.e. if it only ever terminates in response to The examples in MSDN aren’t very helpful, because they all show simple services that just hold |
Thanks for thorough investigation on this matter. I am still wrapping my head around it. Ideally we'd probably want to rely on Control handler function could perhaps rely on global object to synchronize access to and execute |
Yes, a global holding something like a The index is a good idea; I hadn’t thought of that, but now I have a slightly different idea about how you could use it: the context pointer passed to the control handler could be an integer cast to pointer rather than an actual pointer. That could be used as a table index for a multi-service process. I don’t know whether an ever-incrementing index is needed. I assume the reason for that would be to prevent a second run of a service from using the first run’s control handler? I think that’s not really a problem; when the new service starts, it will allocate a fresh shared state (probably |
Correct me if I am wrong, but I don't think
Yeah that's exactly my idea. There is no memory management issue in that case.
Exactly.
Not sure I follow you here. If the type ControlHandler = FnMut(ServiceControl) -> ServiceControlHandlerResult + 'static + Send
static let store: Arc<Mutex<Vec<Option<Box<ControlHandler>>>>> = Arc::new(Mutex::new(Vec::new()))
fn store_control_handler(event_handler: ControlHandler) -> usize {
let shared_data = Arc::clone(&store);
let mut data = shared_data.lock().unwrap();
*data.push(Some(Box::new(event_handler));
*data.len() - 1
}
fn reset_control_handler(index: usize) {
let shared_data = Arc::clone(&store);
let mut data = shared_data.lock().unwrap();
*data[index] = None;
}
fn call_control_handler(index: usize, code: ServiceControl) -> ServiceControlHandlerResult {
let shared_data = Arc::clone(&store);
let data = shared_data.lock().unwrap();
match *data[index] {
Some(boxed_event_handler) => boxed_event_handler(code)
None => ServiceControlHandlerResult::NoError
}
}
pub fn register<S, F>(service_name: S, event_handler: ControlHandler) -> Result<ServiceStatusHandle>
where S: AsRef<OsStr>
{
let mut event_handler_index = store_control_handler(event_handler)
let service_name =
WideCString::from_str(service_name).chain_err(|| ErrorKind::InvalidServiceName)?;
let status_handle = unsafe {
winsvc::RegisterServiceCtrlHandlerExW(
service_name.as_ptr(),
Some(service_control_handler::<F>),
event_handler_index as *mut c_void,
)
};
if status_handle.is_null() {
reset_control_handler(event_handler_index);
Err(io::Error::last_os_error().into())
} else {
Ok(ServiceStatusHandle::from_handle(status_handle))
}
}
/// Static service control handler
#[allow(dead_code)]
extern "system" fn service_control_handler(
control: u32,
_event_type: u32,
_event_data: *mut c_void,
context: *mut c_void,
) -> u32 {
// Important: cast context to &mut F without taking ownership.
let event_handler_index: usize = unsafe { context as usize };
match ServiceControl::from_raw(control) {
Ok(service_control) => {
let return_code = call_control_handler(event_handler_index , service_control).to_raw();
// Important: release context upon Stop, Shutdown or Preshutdown at the end of the
// service lifecycle.
match service_control {
ServiceControl::Stop | ServiceControl::Shutdown | ServiceControl::Preshutdown => {
reset_control_handler(event_handler_index)
}
_ => (),
};
return_code
}
// Report all unknown control commands as unimplemented
Err(_) => ServiceControlHandlerResult::NotImplemented.to_raw(),
}
} But I haven't touched Rust in a while so disregard me if I am talking non-sense. :-) |
You’re correct, of course.
Ah, the point I was trying to make is that, I am reasonably sure that:
Therefore these two actions are interlocked with each other. So I think there is no chance that the old instance’s control handler could be running, after the new instance’s Does that makes sense? |
@Hawk777 makes sense. 👍 |
According to the Microsoft documentation regarding valid state transitions in services, the transition from
RUNNING
toSTOP_PENDING
and the transition fromSTOP_PENDING
toSTOPPED
can both be initiated by the service. That is to say, it appears to be legal for a service to spontaneously decide to stop, rather than stopping in response to a control from the SCM (this makes sense if it fails to initialize, for example).Should a service do this, by doing a
set_service_status
to theStopped
state and then returning from itsServiceMain
function, when using thewindows-service
crate, the control handler will not be invoked withServiceControl::Stop
, therefore thelet _: Box<F> = unsafe { Box::from_raw(context as *mut F) };
line will never execute and the context object will never be dropped.I don’t think it’s possible to fix this without resorting to a global variable, unfortunately (it’s not safe to drop the context object in
ServiceMain
because a race condition could see the control handler being invoked at the same time and accessing the dropped object; I have experimented and determined that there is no mutex in the Windows API betweenSetServiceStatus
and invocations of the control handler that could potentially prevent this from happening). IMO it is reasonable to keep the current behaviour, but I think it should be documented somewhere. For example, the documentation forwindows_service::service_control_handler::register
could mention that, should the service stop spontaneously (rather than in response to aServiceControl::Stop
,Shutdown
, orPreshutdown
), theFnMut
(and anything captured in it, assuming it’s a closure) might be leaked instead of dropped. This is unlikely to be a problem in most cases, but if this crate ever expands to include multi-service processes, it might be a bit more important since we can’t rely on process death to clean up resources (and in any case it’s worth documenting since people might haveDrop
impls that do something important that they’re expecting to run).The text was updated successfully, but these errors were encountered: