Skip to content

Commit 210970c

Browse files
committed
Refactor Application thread ownership
Change how the application manages the ownership of threads, to reduce the amount of initialization the handles during running.
1 parent f2c0e3a commit 210970c

File tree

2 files changed

+110
-50
lines changed

2 files changed

+110
-50
lines changed

src/core/src/application.rs

Lines changed: 109 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ use std::sync::Arc;
22

33
use anyhow::Result;
44
use config::Config;
5-
use display::{Display, Size};
5+
use display::Display;
66
use git::Repository;
77
use input::{EventHandler, EventReaderFn};
88
use parking_lot::Mutex;
9-
use runtime::Runtime;
9+
use runtime::{Runtime, Threadable};
1010
use todo_file::TodoFile;
1111
use view::View;
1212

@@ -20,28 +20,23 @@ use crate::{
2020
Exit,
2121
};
2222

23-
pub(crate) struct Application<ModuleProvider, EventProvider, Tui>
24-
where
25-
ModuleProvider: module::ModuleProvider + Send + 'static,
26-
EventProvider: EventReaderFn,
27-
Tui: display::Tui + 'static,
23+
pub(crate) struct Application<ModuleProvider>
24+
where ModuleProvider: module::ModuleProvider + Send + 'static
2825
{
2926
_config: Config,
3027
_repository: Repository,
31-
todo_file: Arc<Mutex<TodoFile>>,
32-
view: View<Tui>,
33-
event_provider: EventProvider,
34-
initial_display_size: Size,
35-
module_handler: ModuleHandler<ModuleProvider>,
28+
process: Process<ModuleProvider>,
29+
threads: Option<Vec<Box<dyn Threadable>>>,
3630
}
3731

38-
impl<ModuleProvider, EventProvider, Tui> Application<ModuleProvider, EventProvider, Tui>
39-
where
40-
ModuleProvider: module::ModuleProvider + Send + 'static,
41-
EventProvider: EventReaderFn,
42-
Tui: display::Tui + Send + 'static,
32+
impl<ModuleProvider> Application<ModuleProvider>
33+
where ModuleProvider: module::ModuleProvider + Send + 'static
4334
{
44-
pub(crate) fn new(args: &Args, event_provider: EventProvider, tui: Tui) -> Result<Self, Exit> {
35+
pub(crate) fn new<EventProvider, Tui>(args: &Args, event_provider: EventProvider, tui: Tui) -> Result<Self, Exit>
36+
where
37+
EventProvider: EventReaderFn,
38+
Tui: display::Tui + Send + 'static,
39+
{
4540
let filepath = Self::filepath_from_args(args)?;
4641
let repository = Self::open_repository()?;
4742
let config = Self::load_config(&repository)?;
@@ -65,39 +60,46 @@ where
6560
.as_str(),
6661
);
6762

68-
Ok(Self {
69-
_config: config,
70-
_repository: repository,
71-
todo_file,
72-
view,
73-
event_provider,
74-
module_handler,
75-
initial_display_size,
76-
})
77-
}
78-
79-
pub(crate) fn run_until_finished(self) -> Result<(), Exit> {
63+
let mut threads: Vec<Box<dyn Threadable>> = vec![];
8064
let runtime = Runtime::new();
8165

82-
let mut input_threads = events::Thread::new(self.event_provider);
66+
let input_threads = events::Thread::new(event_provider);
8367
let input_state = input_threads.state();
68+
threads.push(Box::new(input_threads));
8469

85-
let mut view_threads = view::Thread::new(self.view);
70+
let view_threads = view::Thread::new(view);
8671
let view_state = view_threads.state();
72+
threads.push(Box::new(view_threads));
8773

8874
let process = Process::new(
89-
self.initial_display_size,
90-
self.todo_file,
91-
self.module_handler,
75+
initial_display_size,
76+
todo_file,
77+
module_handler,
9278
input_state,
9379
view_state,
9480
runtime.statuses(),
9581
);
96-
let mut process_threads = process::Thread::new(process.clone());
82+
let process_threads = process::Thread::new(process.clone());
83+
threads.push(Box::new(process_threads));
84+
85+
Ok(Self {
86+
_config: config,
87+
_repository: repository,
88+
process,
89+
threads: Some(threads),
90+
})
91+
}
92+
93+
pub(crate) fn run_until_finished(&mut self) -> Result<(), Exit> {
94+
let Some(mut threads) = self.threads.take() else {
95+
return Err(Exit::new(ExitStatus::StateError, "Attempt made to run application a second time"));
96+
};
97+
98+
let runtime = Runtime::new();
9799

98-
runtime.register(&mut input_threads);
99-
runtime.register(&mut view_threads);
100-
runtime.register(&mut process_threads);
100+
for thread in &mut threads {
101+
runtime.register(thread.as_mut());
102+
}
101103

102104
runtime.join().map_err(|err| {
103105
Exit::new(
@@ -106,7 +108,7 @@ where
106108
)
107109
})?;
108110

109-
let exit_status = process.exit_status();
111+
let exit_status = self.process.exit_status();
110112
if exit_status != ExitStatus::Good {
111113
return Err(Exit::from(exit_status));
112114
}
@@ -165,8 +167,9 @@ mod tests {
165167
use std::ffi::OsString;
166168

167169
use claim::assert_ok;
168-
use display::testutil::CrossTerm;
170+
use display::{testutil::CrossTerm, Size};
169171
use input::{KeyCode, KeyEvent, KeyModifiers};
172+
use runtime::{Installer, RuntimeError};
170173

171174
use super::*;
172175
use crate::{
@@ -200,7 +203,7 @@ mod tests {
200203
#[serial_test::serial]
201204
fn load_filepath_from_args_failure() {
202205
let event_provider = create_event_reader(|| Ok(None));
203-
let application: Result<Application<TestModuleProvider<DefaultTestModule>, _, _>, Exit> =
206+
let application: Result<Application<TestModuleProvider<DefaultTestModule>>, Exit> =
204207
Application::new(&args(&[]), event_provider, create_mocked_crossterm());
205208
let exit = application_error!(application);
206209
assert_eq!(exit.get_status(), &ExitStatus::StateError);
@@ -217,7 +220,7 @@ mod tests {
217220
fn load_repository_failure() {
218221
let _ = set_git_directory("fixtures/not-a-repository");
219222
let event_provider = create_event_reader(|| Ok(None));
220-
let application: Result<Application<TestModuleProvider<DefaultTestModule>, _, _>, Exit> =
223+
let application: Result<Application<TestModuleProvider<DefaultTestModule>>, Exit> =
221224
Application::new(&args(&["todofile"]), event_provider, create_mocked_crossterm());
222225
let exit = application_error!(application);
223226
assert_eq!(exit.get_status(), &ExitStatus::StateError);
@@ -234,7 +237,7 @@ mod tests {
234237
fn load_config_failure() {
235238
let _ = set_git_directory("fixtures/invalid-config");
236239
let event_provider = create_event_reader(|| Ok(None));
237-
let application: Result<Application<TestModuleProvider<DefaultTestModule>, _, _>, Exit> =
240+
let application: Result<Application<TestModuleProvider<DefaultTestModule>>, Exit> =
238241
Application::new(&args(&["rebase-todo"]), event_provider, create_mocked_crossterm());
239242
let exit = application_error!(application);
240243
assert_eq!(exit.get_status(), &ExitStatus::ConfigError);
@@ -245,7 +248,7 @@ mod tests {
245248
fn load_todo_file_load_error() {
246249
let _ = set_git_directory("fixtures/simple");
247250
let event_provider = create_event_reader(|| Ok(None));
248-
let application: Result<Application<TestModuleProvider<DefaultTestModule>, _, _>, Exit> =
251+
let application: Result<Application<TestModuleProvider<DefaultTestModule>>, Exit> =
249252
Application::new(&args(&["does-not-exist"]), event_provider, create_mocked_crossterm());
250253
let exit = application_error!(application);
251254
assert_eq!(exit.get_status(), &ExitStatus::FileReadError);
@@ -257,7 +260,7 @@ mod tests {
257260
let git_dir = set_git_directory("fixtures/simple");
258261
let rebase_todo = format!("{git_dir}/rebase-todo-noop");
259262
let event_provider = create_event_reader(|| Ok(None));
260-
let application: Result<Application<TestModuleProvider<DefaultTestModule>, _, _>, Exit> = Application::new(
263+
let application: Result<Application<TestModuleProvider<DefaultTestModule>>, Exit> = Application::new(
261264
&args(&[rebase_todo.as_str()]),
262265
event_provider,
263266
create_mocked_crossterm(),
@@ -272,7 +275,7 @@ mod tests {
272275
let git_dir = set_git_directory("fixtures/simple");
273276
let rebase_todo = format!("{git_dir}/rebase-todo-empty");
274277
let event_provider = create_event_reader(|| Ok(None));
275-
let application: Result<Application<TestModuleProvider<DefaultTestModule>, _, _>, Exit> = Application::new(
278+
let application: Result<Application<TestModuleProvider<DefaultTestModule>>, Exit> = Application::new(
276279
&args(&[rebase_todo.as_str()]),
277280
event_provider,
278281
create_mocked_crossterm(),
@@ -293,7 +296,7 @@ mod tests {
293296
let git_dir = set_git_directory("fixtures/simple");
294297
let rebase_todo = format!("{git_dir}/rebase-todo");
295298
let event_provider = create_event_reader(|| Ok(Some(Event::Key(KeyEvent::from(KeyCode::Char('W'))))));
296-
let application: Application<Modules, _, _> = Application::new(
299+
let mut application: Application<Modules> = Application::new(
297300
&args(&[rebase_todo.as_str()]),
298301
event_provider,
299302
create_mocked_crossterm(),
@@ -302,6 +305,42 @@ mod tests {
302305
assert_ok!(application.run_until_finished());
303306
}
304307

308+
#[test]
309+
#[serial_test::serial]
310+
fn run_join_error() {
311+
struct FailingThread;
312+
impl Threadable for FailingThread {
313+
fn install(&self, installer: &Installer) {
314+
installer.spawn("THREAD", |notifier| {
315+
move || {
316+
notifier.error(RuntimeError::ThreadSpawnError(String::from("Error")));
317+
}
318+
});
319+
}
320+
}
321+
322+
let git_dir = set_git_directory("fixtures/simple");
323+
let rebase_todo = format!("{git_dir}/rebase-todo");
324+
let event_provider = create_event_reader(|| Ok(Some(Event::Key(KeyEvent::from(KeyCode::Char('W'))))));
325+
let mut application: Application<Modules> = Application::new(
326+
&args(&[rebase_todo.as_str()]),
327+
event_provider,
328+
create_mocked_crossterm(),
329+
)
330+
.unwrap();
331+
332+
application.threads = Some(vec![Box::new(FailingThread {})]);
333+
334+
let exit = application.run_until_finished().unwrap_err();
335+
assert_eq!(exit.get_status(), &ExitStatus::StateError);
336+
assert!(
337+
exit.get_message()
338+
.as_ref()
339+
.unwrap()
340+
.starts_with("Failed to join runtime:")
341+
);
342+
}
343+
305344
#[test]
306345
#[serial_test::serial]
307346
fn run_until_finished_kill() {
@@ -313,7 +352,7 @@ mod tests {
313352
KeyModifiers::CONTROL,
314353
))))
315354
});
316-
let application: Application<Modules, _, _> = Application::new(
355+
let mut application: Application<Modules> = Application::new(
317356
&args(&[rebase_todo.as_str()]),
318357
event_provider,
319358
create_mocked_crossterm(),
@@ -322,4 +361,25 @@ mod tests {
322361
let exit = application.run_until_finished().unwrap_err();
323362
assert_eq!(exit.get_status(), &ExitStatus::Kill);
324363
}
364+
365+
#[test]
366+
#[serial_test::serial]
367+
fn run_error_on_second_attempt() {
368+
let git_dir = set_git_directory("fixtures/simple");
369+
let rebase_todo = format!("{git_dir}/rebase-todo");
370+
let event_provider = create_event_reader(|| Ok(Some(Event::Key(KeyEvent::from(KeyCode::Char('W'))))));
371+
let mut application: Application<Modules> = Application::new(
372+
&args(&[rebase_todo.as_str()]),
373+
event_provider,
374+
create_mocked_crossterm(),
375+
)
376+
.unwrap();
377+
assert_ok!(application.run_until_finished());
378+
let exit = application.run_until_finished().unwrap_err();
379+
assert_eq!(exit.get_status(), &ExitStatus::StateError);
380+
assert_eq!(
381+
exit.get_message().as_ref().unwrap(),
382+
"Attempt made to run application a second time"
383+
);
384+
}
325385
}

src/core/src/editor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::{
1313

1414
#[cfg(not(tarpaulin_include))]
1515
pub(crate) fn run(args: &Args) -> Exit {
16-
let application: Application<Modules, _, _> = match Application::new(args, read_event, CrossTerm::new()) {
16+
let mut application: Application<Modules> = match Application::new(args, read_event, CrossTerm::new()) {
1717
Ok(app) => app,
1818
Err(exit) => return exit,
1919
};

0 commit comments

Comments
 (0)