Skip to content

Commit bfe5cc4

Browse files
committed
Redo some thread handing in the clock to avoid deadlocks on windows and macos
1 parent 9603d30 commit bfe5cc4

File tree

8 files changed

+54
-33
lines changed

8 files changed

+54
-33
lines changed

src/core/Clock.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ void Core::Clock::stop() {
5858
void Core::Clock::halt() {
5959
halted = true;
6060
stop();
61-
clockThread.detach(); // To allow creating a new thread without crashing if you don't join() first
6261
}
6362

6463
void Core::Clock::join() {
@@ -67,6 +66,12 @@ void Core::Clock::join() {
6766
}
6867
}
6968

69+
void Core::Clock::detach() {
70+
if (clockThread.joinable()) {
71+
clockThread.detach();
72+
}
73+
}
74+
7075
void Core::Clock::singleStep() {
7176
std::cout << "Clock: single stepping clock" << std::endl;
7277

src/core/Clock.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ namespace Core {
2828
~Clock();
2929

3030
/**
31-
* Start the clock asynchronously.
32-
* Will continue until stopped programmatically or through the HLT instruction.
31+
* Start the clock asynchronously. Will continue until stop() or halt().
32+
* Note: you need to either call join() or detach() right afterwards otherwise you may experience
33+
* crashes or deadlocks.
3334
*/
3435
void start();
3536

@@ -42,6 +43,9 @@ namespace Core {
4243
/** Wait for an asynchronous clock while it's running. */
4344
void join();
4445

46+
/** Ignore an asynchronous clock while it's running. */
47+
void detach();
48+
4549
/** Run one clock cycle synchronously and then stop. */
4650
void singleStep();
4751

src/core/Emulator.cpp

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,22 @@ void Core::Emulator::reload() {
6767
initializeProgram();
6868
}
6969

70-
void Core::Emulator::run() {
70+
void Core::Emulator::startAsynchronous() {
7171
if (Utils::debugL1()) {
72-
std::cout << "Emulator: run start" << std::endl;
72+
std::cout << "Emulator: start asynchronous" << std::endl;
7373
}
7474

7575
clock->start();
76+
clock->detach();
77+
}
78+
79+
void Core::Emulator::startSynchronous() {
80+
if (Utils::debugL1()) {
81+
std::cout << "Emulator: start synchronous" << std::endl;
82+
}
83+
84+
clock->start();
85+
clock->join();
7686
}
7787

7888
void Core::Emulator::initializeProgram() {
@@ -97,10 +107,6 @@ bool Core::Emulator::isRunning() {
97107
return clock->isRunning();
98108
}
99109

100-
void Core::Emulator::waitUntilFinished() {
101-
clock->join();
102-
}
103-
104110
void Core::Emulator::singleStep() {
105111
clock->singleStep();
106112
}

src/core/Emulator.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ namespace Core {
3434
void reload();
3535

3636
/** Start running the loaded program. Asynchronous. */
37-
void run();
37+
void startAsynchronous();
3838

39-
/** Wait for the program to finish, or for the emulator to finish stopping. */
40-
void waitUntilFinished();
39+
/** Start running the loaded program. Synchronous. */
40+
void startSynchronous();
4141

4242
/** Whether the emulator is currently running a program. */
4343
bool isRunning();

src/ui/Keyboard.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@ void UI::Keyboard::keyUp(const SDL_Keycode keycode) {
2323
if (keycode == SDLK_s) {
2424
if (emulator->isRunning()) {
2525
emulator->stop();
26-
emulator->waitUntilFinished();
2726
} else {
28-
emulator->run();
27+
emulator->startAsynchronous();
2928
}
3029
}
3130

src/ui/UserInterface.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ void UI::UserInterface::mainLoop() {
9999
}
100100

101101
emulator->stop();
102-
emulator->waitUntilFinished();
103102
}
104103

105104
void UI::UserInterface::drawLeftColumn() {

test/core/ClockTest.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,20 @@ TEST_SUITE("ClockTest") {
241241
fakeit::VerifyNoOtherInvocations(listenerMock);
242242
}
243243

244+
SUBCASE("detach() should allow restarting the clock without join()") {
245+
clock.setFrequency(5);
246+
clock.start();
247+
248+
clock.detach();
249+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
250+
251+
clock.stop();
252+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
253+
CHECK_FALSE(clock.isRunning());
254+
255+
clock.singleStep();
256+
}
257+
244258
SUBCASE("setFrequency() should notify observer") {
245259
fakeit::Mock<ClockObserver> observerMock;
246260
auto observerPtr = std::shared_ptr<ClockObserver>(&observerMock(), [](...) {});

test/core/EmulatorIntegrationTest.cpp

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,55 +27,50 @@ TEST_SUITE("EmulatorIntegrationTest") {
2727
"Emulator: no instructions loaded. Aborting");
2828
}
2929

30-
SUBCASE("run() should complete nop_test.asm") {
30+
SUBCASE("startSynchronous() should complete nop_test.asm") {
3131
emulator.load("../../programs/nop_test.asm");
3232

33-
emulator.run();
34-
emulator.waitUntilFinished();
33+
emulator.startSynchronous();
3534

3635
fakeit::Verify(Method(observerMock, valueUpdated).Using(0)).Once(); // Reset before start
3736
fakeit::Verify(Method(observerMock, valueUpdated).Using(10)).Once();
3837
fakeit::VerifyNoOtherInvocations(observerMock);
3938
}
4039

41-
SUBCASE("run() should complete add_two_numbers.asm") {
40+
SUBCASE("startSynchronous() should complete add_two_numbers.asm") {
4241
emulator.load("../../programs/add_two_numbers.asm");
4342

44-
emulator.run();
45-
emulator.waitUntilFinished();
43+
emulator.startSynchronous();
4644

4745
fakeit::Verify(Method(observerMock, valueUpdated).Using(0)).Once(); // Reset before start
4846
fakeit::Verify(Method(observerMock, valueUpdated).Using(42)).Once(); // 28+14
4947
fakeit::VerifyNoOtherInvocations(observerMock);
5048
}
5149

52-
SUBCASE("run() should complete subtract_two_numbers.asm") {
50+
SUBCASE("startSynchronous() should complete subtract_two_numbers.asm") {
5351
emulator.load("../../programs/subtract_two_numbers.asm");
5452

55-
emulator.run();
56-
emulator.waitUntilFinished();
53+
emulator.startSynchronous();
5754

5855
fakeit::Verify(Method(observerMock, valueUpdated).Using(0)).Once(); // Reset before start
5956
fakeit::Verify(Method(observerMock, valueUpdated).Using(18)).Once(); // 30-12
6057
fakeit::VerifyNoOtherInvocations(observerMock);
6158
}
6259

63-
SUBCASE("run() should complete multiply_two_numbers.asm") {
60+
SUBCASE("startSynchronous() should complete multiply_two_numbers.asm") {
6461
emulator.load("../../programs/multiply_two_numbers.asm");
6562

66-
emulator.run();
67-
emulator.waitUntilFinished();
63+
emulator.startSynchronous();
6864

6965
fakeit::Verify(Method(observerMock, valueUpdated).Using(0)).Once(); // Reset before start
7066
fakeit::Verify(Method(observerMock, valueUpdated).Using(56)).Once(); // 7*8
7167
fakeit::VerifyNoOtherInvocations(observerMock);
7268
}
7369

74-
SUBCASE("run() should complete count_0_255_stop.asm") {
70+
SUBCASE("startSynchronous() should complete count_0_255_stop.asm") {
7571
emulator.load("../../programs/count_0_255_stop.asm");
7672

77-
emulator.run();
78-
emulator.waitUntilFinished();
73+
emulator.startSynchronous();
7974

8075
// Once for reset, and once when counting
8176
fakeit::Verify(Method(observerMock, valueUpdated).Using(0)).Twice();
@@ -87,11 +82,10 @@ TEST_SUITE("EmulatorIntegrationTest") {
8782
fakeit::VerifyNoOtherInvocations(observerMock);
8883
}
8984

90-
SUBCASE("run() should complete count_255_0_stop.asm") {
85+
SUBCASE("startSynchronous() should complete count_255_0_stop.asm") {
9186
emulator.load("../../programs/count_255_0_stop.asm");
9287

93-
emulator.run();
94-
emulator.waitUntilFinished();
88+
emulator.startSynchronous();
9589

9690
// Once for reset, and once when counting
9791
fakeit::Verify(Method(observerMock, valueUpdated).Using(0)).Twice();

0 commit comments

Comments
 (0)