Skip to content

Commit f1d8a93

Browse files
iMithrellaspront
andauthored
fix(windows platform): correct stop/start state checks and add configurable stop timeout (#24772)
* fix(windows service): wait for stopped state on stop * enhancement(windows service): make stop wait timeout configurable for stop and uninstall --------- Co-authored-by: Pavlos Rontidis <pavlos.rontidis@gmail.com>
1 parent 93a9771 commit f1d8a93

3 files changed

Lines changed: 100 additions & 32 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fixed Windows service state checks in `vector service start`/`stop`, and made `vector service stop` wait until the service reaches `Stopped`. Added `--stop-timeout` to `vector service stop` and `vector service uninstall`.
2+
3+
authors: iMithrellas

src/service.rs

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,18 @@ impl InstallOpts {
101101
}
102102
}
103103

104+
#[derive(Parser, Debug)]
105+
#[command(rename_all = "kebab-case")]
106+
struct UninstallOpts {
107+
/// The name of the service.
108+
#[arg(long)]
109+
name: Option<String>,
110+
111+
/// How long to wait for the service to stop before uninstalling, in seconds.
112+
#[arg(default_value = "10", long)]
113+
stop_timeout: u32,
114+
}
115+
104116
#[derive(Parser, Debug)]
105117
#[command(rename_all = "kebab-case")]
106118
struct RestartOpts {
@@ -113,6 +125,18 @@ struct RestartOpts {
113125
stop_timeout: u32,
114126
}
115127

128+
#[derive(Parser, Debug)]
129+
#[command(rename_all = "kebab-case")]
130+
struct StopOpts {
131+
/// The name of the service.
132+
#[arg(long)]
133+
name: Option<String>,
134+
135+
/// How long to wait for the service to stop, in seconds.
136+
#[arg(default_value = "10", long)]
137+
stop_timeout: u32,
138+
}
139+
116140
impl RestartOpts {
117141
fn service_info(&self) -> ServiceInfo {
118142
let mut default_service = ServiceInfo::default();
@@ -123,6 +147,26 @@ impl RestartOpts {
123147
}
124148
}
125149

150+
impl UninstallOpts {
151+
fn service_info(&self) -> ServiceInfo {
152+
let mut default_service = ServiceInfo::default();
153+
let service_name = self.name.as_deref().unwrap_or(DEFAULT_SERVICE_NAME);
154+
155+
default_service.name = OsString::from(service_name);
156+
default_service
157+
}
158+
}
159+
160+
impl StopOpts {
161+
fn service_info(&self) -> ServiceInfo {
162+
let mut default_service = ServiceInfo::default();
163+
let service_name = self.name.as_deref().unwrap_or(DEFAULT_SERVICE_NAME);
164+
165+
default_service.name = OsString::from(service_name);
166+
default_service
167+
}
168+
}
169+
126170
#[derive(Parser, Debug)]
127171
#[command(rename_all = "kebab-case")]
128172
struct StandardOpts {
@@ -147,11 +191,11 @@ enum SubCommand {
147191
/// Install the service.
148192
Install(InstallOpts),
149193
/// Uninstall the service.
150-
Uninstall(StandardOpts),
194+
Uninstall(UninstallOpts),
151195
/// Start the service.
152196
Start(StandardOpts),
153197
/// Stop the service.
154-
Stop(StandardOpts),
198+
Stop(StopOpts),
155199
/// Restart the service.
156200
Restart(RestartOpts),
157201
}
@@ -182,9 +226,9 @@ impl Default for ServiceInfo {
182226
#[derive(Debug, Clone, PartialEq)]
183227
enum ControlAction {
184228
Install,
185-
Uninstall,
229+
Uninstall { stop_timeout: Duration },
186230
Start,
187-
Stop,
231+
Stop { stop_timeout: Duration },
188232
Restart { stop_timeout: Duration },
189233
}
190234

@@ -196,10 +240,17 @@ pub fn cmd(opts: &Opts) -> exitcode::ExitCode {
196240
control_service(&opts.service_info(), ControlAction::Install)
197241
}
198242
SubCommand::Uninstall(opts) => {
199-
control_service(&opts.service_info(), ControlAction::Uninstall)
243+
let stop_timeout = Duration::from_secs(opts.stop_timeout as u64);
244+
control_service(
245+
&opts.service_info(),
246+
ControlAction::Uninstall { stop_timeout },
247+
)
200248
}
201249
SubCommand::Start(opts) => control_service(&opts.service_info(), ControlAction::Start),
202-
SubCommand::Stop(opts) => control_service(&opts.service_info(), ControlAction::Stop),
250+
SubCommand::Stop(opts) => {
251+
let stop_timeout = Duration::from_secs(opts.stop_timeout as u64);
252+
control_service(&opts.service_info(), ControlAction::Stop { stop_timeout })
253+
}
203254
SubCommand::Restart(opts) => {
204255
let stop_timeout = Duration::from_secs(opts.stop_timeout as u64);
205256
control_service(
@@ -233,17 +284,17 @@ fn control_service(service: &ServiceInfo, action: ControlAction) -> exitcode::Ex
233284
&service_definition,
234285
vector_windows::service_control::ControlAction::Install,
235286
),
236-
ControlAction::Uninstall => vector_windows::service_control::control(
287+
ControlAction::Uninstall { stop_timeout } => vector_windows::service_control::control(
237288
&service_definition,
238-
vector_windows::service_control::ControlAction::Uninstall,
289+
vector_windows::service_control::ControlAction::Uninstall { stop_timeout },
239290
),
240291
ControlAction::Start => vector_windows::service_control::control(
241292
&service_definition,
242293
vector_windows::service_control::ControlAction::Start,
243294
),
244-
ControlAction::Stop => vector_windows::service_control::control(
295+
ControlAction::Stop { stop_timeout } => vector_windows::service_control::control(
245296
&service_definition,
246-
vector_windows::service_control::ControlAction::Stop,
297+
vector_windows::service_control::ControlAction::Stop { stop_timeout },
247298
),
248299
ControlAction::Restart { stop_timeout } => vector_windows::service_control::control(
249300
&service_definition,

src/vector_windows.rs

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,9 @@ pub mod service_control {
8181
#[derive(Debug, Copy, Clone, PartialEq)]
8282
pub enum ControlAction {
8383
Install,
84-
Uninstall,
84+
Uninstall { stop_timeout: Duration },
8585
Start,
86-
Stop,
86+
Stop { stop_timeout: Duration },
8787
Restart { stop_timeout: Duration },
8888
}
8989

@@ -114,9 +114,13 @@ pub mod service_control {
114114
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
115115
match s {
116116
"install" => Ok(ControlAction::Install),
117-
"uninstall" => Ok(ControlAction::Uninstall),
117+
"uninstall" => Ok(ControlAction::Uninstall {
118+
stop_timeout: Duration::from_secs(10),
119+
}),
118120
"start" => Ok(ControlAction::Start),
119-
"stop" => Ok(ControlAction::Stop),
121+
"stop" => Ok(ControlAction::Stop {
122+
stop_timeout: Duration::from_secs(10),
123+
}),
120124
_ => Err(format!("invalid option {} for ControlAction", s)),
121125
}
122126
}
@@ -125,10 +129,12 @@ pub mod service_control {
125129
pub fn control(service_def: &ServiceDefinition, action: ControlAction) -> crate::Result<()> {
126130
match action {
127131
ControlAction::Start => start_service(service_def),
128-
ControlAction::Stop => stop_service(service_def),
132+
ControlAction::Stop { stop_timeout } => stop_service(service_def, stop_timeout),
129133
ControlAction::Restart { stop_timeout } => restart_service(service_def, stop_timeout),
130134
ControlAction::Install => install_service(service_def),
131-
ControlAction::Uninstall => uninstall_service(service_def),
135+
ControlAction::Uninstall { stop_timeout } => {
136+
uninstall_service(service_def, stop_timeout)
137+
}
132138
}
133139
}
134140

@@ -138,7 +144,7 @@ pub mod service_control {
138144
let service_status = service.query_status().context(ServiceSnafu)?;
139145

140146
if service_status.current_state != ServiceState::StartPending
141-
|| service_status.current_state != ServiceState::Running
147+
&& service_status.current_state != ServiceState::Running
142148
{
143149
service.start(&[] as &[OsString]).context(ServiceSnafu)?;
144150
emit!(WindowsServiceStart {
@@ -155,26 +161,31 @@ pub mod service_control {
155161
Ok(())
156162
}
157163

158-
fn stop_service(service_def: &ServiceDefinition) -> crate::Result<()> {
164+
fn stop_service(service_def: &ServiceDefinition, stop_timeout: Duration) -> crate::Result<()> {
159165
let service_access = ServiceAccess::QUERY_STATUS | ServiceAccess::STOP;
160166
let service = open_service(service_def, service_access)?;
161167
let service_status = service.query_status().context(ServiceSnafu)?;
168+
let already_stopped = service_status.current_state == ServiceState::Stopped;
162169

163-
if service_status.current_state != ServiceState::StopPending
164-
|| service_status.current_state != ServiceState::Stopped
165-
{
170+
if service_status.current_state != ServiceState::StopPending && !already_stopped {
166171
service.stop().context(ServiceSnafu)?;
167-
emit!(WindowsServiceStop {
168-
name: &*service_def.name.to_string_lossy(),
169-
already_stopped: false,
170-
});
171-
} else {
172-
emit!(WindowsServiceStop {
173-
name: &*service_def.name.to_string_lossy(),
174-
already_stopped: true,
175-
});
176172
}
177173

174+
if !already_stopped {
175+
let service_status = ensure_state(
176+
&service,
177+
ServiceState::Stopped,
178+
stop_timeout,
179+
Duration::from_secs(1),
180+
)?;
181+
handle_service_exit_code(service_status.exit_code);
182+
}
183+
184+
emit!(WindowsServiceStop {
185+
name: &*service_def.name.to_string_lossy(),
186+
already_stopped,
187+
});
188+
178189
Ok(())
179190
}
180191

@@ -242,7 +253,10 @@ pub mod service_control {
242253
Ok(())
243254
}
244255

245-
fn uninstall_service(service_def: &ServiceDefinition) -> crate::Result<()> {
256+
fn uninstall_service(
257+
service_def: &ServiceDefinition,
258+
stop_timeout: Duration,
259+
) -> crate::Result<()> {
246260
let service_access =
247261
ServiceAccess::QUERY_STATUS | ServiceAccess::STOP | ServiceAccess::DELETE;
248262
let service = open_service(service_def, service_access)?;
@@ -259,7 +273,7 @@ pub mod service_control {
259273
let service_status = ensure_state(
260274
&service,
261275
ServiceState::Stopped,
262-
Duration::from_secs(10),
276+
stop_timeout,
263277
Duration::from_secs(1),
264278
)?;
265279
handle_service_exit_code(service_status.exit_code);

0 commit comments

Comments
 (0)