Skip to content

Commit d128e80

Browse files
unhappychoiceclaude
andcommitted
refactor: improve EventBus to prevent deadlocks and remove unused events
- Change BoxedHandler from Box to Arc to enable cloning - Clone handlers before releasing lock in publish() to prevent deadlocks - Remove unused domain events (TypingStarted, ChallengeCompleted, StageCompleted, SessionCompleted, ScoreCalculated, ErrorOccurred) - Consolidate remaining events into unified DomainEvent enum - Remove unused ui_events.rs module This fixes deadlocks that occurred when EventBus handlers tried to acquire locks while publish() was holding the subscribers lock. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 43f9014 commit d128e80

File tree

5 files changed

+24
-285
lines changed

5 files changed

+24
-285
lines changed

src/domain/events/domain_events.rs

Lines changed: 10 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,19 @@
11
use super::Event;
2-
use crate::domain::models::chunk::CodeChunk;
3-
use crate::domain::models::rank::Rank;
42
use std::any::Any;
5-
use std::time::Instant;
63

4+
// Unified domain event enum for pattern matching
75
#[derive(Debug, Clone)]
8-
pub struct TypingStarted {
9-
pub challenge_id: i64,
10-
pub timestamp: Instant,
6+
pub enum DomainEvent {
7+
KeyPressed { key: char, position: usize },
8+
StageStarted { start_time: std::time::Instant },
9+
StagePaused,
10+
StageResumed,
11+
StageFinalized,
12+
StageSkipped,
13+
ChallengeLoaded { text: String, source_path: String },
1114
}
1215

13-
impl Event for TypingStarted {
14-
fn as_any(&self) -> &dyn Any {
15-
self
16-
}
17-
}
18-
19-
#[derive(Debug, Clone)]
20-
pub struct KeyPressed {
21-
pub key: char,
22-
pub timestamp: Instant,
23-
}
24-
25-
impl Event for KeyPressed {
26-
fn as_any(&self) -> &dyn Any {
27-
self
28-
}
29-
}
30-
31-
#[derive(Debug, Clone)]
32-
pub struct ChallengeCompleted {
33-
pub challenge_id: i64,
34-
pub timestamp: Instant,
35-
}
36-
37-
impl Event for ChallengeCompleted {
38-
fn as_any(&self) -> &dyn Any {
39-
self
40-
}
41-
}
42-
43-
#[derive(Debug, Clone)]
44-
pub struct StageCompleted {
45-
pub stage_id: i64,
46-
pub chunk: CodeChunk,
47-
pub timestamp: Instant,
48-
}
49-
50-
impl Event for StageCompleted {
51-
fn as_any(&self) -> &dyn Any {
52-
self
53-
}
54-
}
55-
56-
#[derive(Debug, Clone)]
57-
pub struct SessionCompleted {
58-
pub session_id: i64,
59-
pub timestamp: Instant,
60-
}
61-
62-
impl Event for SessionCompleted {
63-
fn as_any(&self) -> &dyn Any {
64-
self
65-
}
66-
}
67-
68-
#[derive(Debug, Clone)]
69-
pub struct ScoreCalculated {
70-
pub score: f64,
71-
pub accuracy: f64,
72-
pub wpm: f64,
73-
pub rank: Rank,
74-
}
75-
76-
impl Event for ScoreCalculated {
77-
fn as_any(&self) -> &dyn Any {
78-
self
79-
}
80-
}
81-
82-
#[derive(Debug, Clone)]
83-
pub struct ErrorOccurred {
84-
pub message: String,
85-
pub timestamp: Instant,
86-
}
87-
88-
impl Event for ErrorOccurred {
16+
impl Event for DomainEvent {
8917
fn as_any(&self) -> &dyn Any {
9018
self
9119
}

src/domain/events/mod.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use std::collections::HashMap;
33
use std::sync::{Arc, RwLock};
44

55
pub mod domain_events;
6-
pub mod ui_events;
76

87
pub trait Event: Send + Sync + 'static {
98
fn as_any(&self) -> &dyn Any;
@@ -13,7 +12,7 @@ pub trait EventHandler<E: Event>: Send + Sync {
1312
fn handle(&self, event: &E);
1413
}
1514

16-
type BoxedHandler = Box<dyn Fn(&dyn Event) + Send + Sync>;
15+
type BoxedHandler = Arc<dyn Fn(&dyn Event) + Send + Sync>;
1716

1817
pub struct EventBus {
1918
subscribers: Arc<RwLock<HashMap<TypeId, Vec<BoxedHandler>>>>,
@@ -28,12 +27,19 @@ impl EventBus {
2827

2928
pub fn publish<E: Event>(&self, event: E) {
3029
let type_id = TypeId::of::<E>();
31-
let subscribers = self.subscribers.read().unwrap();
3230

33-
if let Some(handlers) = subscribers.get(&type_id) {
34-
for handler in handlers {
35-
handler(&event);
36-
}
31+
// Clone Arc<handlers> while holding the lock, then release it before calling them
32+
let handlers: Vec<BoxedHandler> = {
33+
let subscribers = self.subscribers.read().unwrap();
34+
let h = subscribers.get(&type_id)
35+
.map(|h| h.clone())
36+
.unwrap_or_default();
37+
h
38+
}; // Lock is released here
39+
40+
// Call handlers without holding the lock
41+
for handler in handlers.iter() {
42+
handler(&event);
3743
}
3844
}
3945

@@ -44,7 +50,7 @@ impl EventBus {
4450
let type_id = TypeId::of::<E>();
4551
let mut subscribers = self.subscribers.write().unwrap();
4652

47-
let boxed_handler: BoxedHandler = Box::new(move |event: &dyn Event| {
53+
let boxed_handler: BoxedHandler = Arc::new(move |event: &dyn Event| {
4854
if let Some(concrete_event) = event.as_any().downcast_ref::<E>() {
4955
handler(concrete_event);
5056
}

src/domain/events/ui_events.rs

Lines changed: 0 additions & 48 deletions
This file was deleted.

tests/integration/event_flow_tests.rs

Lines changed: 0 additions & 146 deletions
This file was deleted.

tests/integration/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
pub mod ascii_art_coverage_tests;
22
pub mod comment_processing_tests;
3-
pub mod event_flow_tests;
43
pub mod indent_treesitter_tests;
54
pub mod languages;
65
pub mod missing_ascii_art_test;

0 commit comments

Comments
 (0)