Skip to content

Commit 0159293

Browse files
committed
Properly implement timer using dedicated timer thread
1 parent e187cd8 commit 0159293

File tree

7 files changed

+202
-47
lines changed

7 files changed

+202
-47
lines changed

java_runtime/src/classes/java/util.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ mod stack;
1616
mod time_zone;
1717
mod timer;
1818
mod timer_task;
19+
mod timer_thread;
1920
mod vector;
2021

2122
pub use self::{
2223
abstract_collection::AbstractCollection, abstract_list::AbstractList, calendar::Calendar, date::Date, dictionary::Dictionary,
2324
empty_stack_exception::EmptyStackException, enumeration::Enumeration, gregorian_calendar::GregorianCalendar, hashtable::Hashtable,
24-
properties::Properties, random::Random, stack::Stack, time_zone::TimeZone, timer::Timer, timer_task::TimerTask, vector::Vector,
25+
properties::Properties, random::Random, stack::Stack, time_zone::TimeZone, timer::Timer, timer_task::TimerTask, timer_thread::TimerThread,
26+
vector::Vector,
2527
};

java_runtime/src/classes/java/util/timer.rs

Lines changed: 62 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
use alloc::{boxed::Box, vec};
1+
use alloc::vec;
22

3-
use java_class_proto::JavaMethodProto;
3+
use java_class_proto::{JavaFieldProto, JavaMethodProto};
44
use jvm::{ClassInstanceRef, Jvm, Result};
55

6-
use crate::{RuntimeClassProto, RuntimeContext, SpawnCallback, classes::java::util::TimerTask};
6+
use crate::{
7+
RuntimeClassProto, RuntimeContext,
8+
classes::java::util::{TimerTask, Vector},
9+
};
710

811
// class java.util.Timer
912
pub struct Timer;
@@ -17,16 +20,35 @@ impl Timer {
1720
methods: vec![
1821
JavaMethodProto::new("<init>", "()V", Self::init, Default::default()),
1922
JavaMethodProto::new("schedule", "(Ljava/util/TimerTask;JJ)V", Self::schedule, Default::default()),
23+
JavaMethodProto::new(
24+
"scheduleAtFixedRate",
25+
"(Ljava/util/TimerTask;JJ)V",
26+
Self::schedule_at_fixed_rate,
27+
Default::default(),
28+
),
29+
],
30+
fields: vec![
31+
JavaFieldProto::new("tasks", "Ljava/util/Vector;", Default::default()),
32+
JavaFieldProto::new("thread", "Ljava/lang/Thread;", Default::default()),
2033
],
21-
fields: vec![],
2234
}
2335
}
2436

25-
async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef<Self>) -> Result<()> {
37+
async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef<Self>) -> Result<()> {
2638
tracing::debug!("java.util.Timer::<init>({:?})", &this);
2739

2840
let _: () = jvm.invoke_special(&this, "java/lang/Object", "<init>", "()V", ()).await?;
2941

42+
let tasks = jvm.new_class("java/util/Vector", "()V", ()).await?;
43+
let timer_thread = jvm
44+
.new_class("java/util/Timer$TimerThread", "(Ljava/util/Vector;)V", (tasks.clone(),))
45+
.await?;
46+
47+
jvm.put_field(&mut this, "tasks", "Ljava/util/Vector;", tasks).await?;
48+
jvm.put_field(&mut this, "thread", "Ljava/lang/Thread;", timer_thread.clone()).await?;
49+
50+
let _: () = jvm.invoke_virtual(&timer_thread, "start", "()V", ()).await?;
51+
3052
Ok(())
3153
}
3254

@@ -40,40 +62,47 @@ impl Timer {
4062
) -> Result<()> {
4163
tracing::debug!("java.util.Timer::schedule({:?}, {:?}, {:?}, {:?})", &this, &task, delay, period);
4264

43-
// TODO we should not spawn new thread every time
65+
let now: i64 = context.now() as i64;
66+
let next_execution_time = now + delay;
4467

45-
struct TimerProxy {
46-
jvm: Jvm,
47-
task: ClassInstanceRef<TimerTask>,
48-
delay: i64,
49-
period: i64,
50-
}
68+
Self::do_schedule(jvm, this, task, next_execution_time, period).await
69+
}
5170

52-
#[async_trait::async_trait]
53-
impl SpawnCallback for TimerProxy {
54-
#[tracing::instrument(name = "timer", skip_all)]
55-
async fn call(&self) -> Result<()> {
56-
self.jvm.attach_thread()?;
71+
async fn schedule_at_fixed_rate(
72+
jvm: &Jvm,
73+
context: &mut RuntimeContext,
74+
this: ClassInstanceRef<Self>,
75+
task: ClassInstanceRef<TimerTask>,
76+
delay: i64,
77+
period: i64,
78+
) -> Result<()> {
79+
tracing::debug!(
80+
"java.util.Timer::scheduleAtFixedRate({:?}, {:?}, {:?}, {:?})",
81+
&this,
82+
&task,
83+
delay,
84+
period
85+
);
86+
// FIXME: fixed rate is not different from normal rate
5787

58-
let _: () = self.jvm.invoke_static("java/lang/Thread", "sleep", "(J)V", (self.delay,)).await?;
88+
let now: i64 = context.now() as i64;
89+
let next_execution_time = now + delay;
5990

60-
loop {
61-
let _: () = self.jvm.invoke_virtual(&self.task, "run", "()V", ()).await?;
91+
Self::do_schedule(jvm, this, task, next_execution_time, period).await
92+
}
6293

63-
let _: () = self.jvm.invoke_static("java/lang/Thread", "sleep", "(J)V", (self.period,)).await?;
64-
}
65-
}
66-
}
94+
async fn do_schedule(
95+
jvm: &Jvm,
96+
this: ClassInstanceRef<Self>,
97+
mut task: ClassInstanceRef<TimerTask>,
98+
next_execution_time: i64,
99+
period: i64,
100+
) -> Result<()> {
101+
jvm.put_field(&mut task, "nextExecutionTime", "J", next_execution_time).await?;
102+
jvm.put_field(&mut task, "period", "J", period).await?;
67103

68-
context.spawn(
69-
jvm,
70-
Box::new(TimerProxy {
71-
jvm: jvm.clone(),
72-
task,
73-
delay,
74-
period,
75-
}),
76-
);
104+
let tasks: ClassInstanceRef<Vector> = jvm.get_field(&this, "tasks", "Ljava/util/Vector;").await?;
105+
let _: bool = jvm.invoke_virtual(&tasks, "add", "(Ljava/lang/Object;)Z", (task,)).await?;
77106

78107
Ok(())
79108
}

java_runtime/src/classes/java/util/timer_task.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use alloc::vec;
22

3-
use java_class_proto::JavaMethodProto;
3+
use java_class_proto::{JavaFieldProto, JavaMethodProto};
44
use jvm::{ClassInstanceRef, Jvm, Result};
55

66
use crate::{RuntimeClassProto, RuntimeContext};
@@ -18,7 +18,10 @@ impl TimerTask {
1818
JavaMethodProto::new("<init>", "()V", Self::init, Default::default()),
1919
JavaMethodProto::new_abstract("run", "()V", Default::default()),
2020
],
21-
fields: vec![],
21+
fields: vec![
22+
JavaFieldProto::new("nextExecutionTime", "J", Default::default()),
23+
JavaFieldProto::new("period", "J", Default::default()),
24+
],
2225
}
2326
}
2427

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
use alloc::{vec, vec::Vec};
2+
use core::time::Duration;
3+
4+
use java_class_proto::{JavaFieldProto, JavaMethodProto};
5+
use jvm::{ClassInstanceRef, Jvm, Result};
6+
7+
use crate::{RuntimeClassProto, RuntimeContext, classes::java::util::Vector};
8+
9+
// class java.util.Timer$TimerThread
10+
pub struct TimerThread;
11+
12+
impl TimerThread {
13+
pub fn as_proto() -> RuntimeClassProto {
14+
RuntimeClassProto {
15+
name: "java/util/Timer$TimerThread",
16+
parent_class: Some("java/lang/Thread"),
17+
interfaces: vec![],
18+
methods: vec![
19+
JavaMethodProto::new("<init>", "(Ljava/util/Vector;)V", Self::init, Default::default()),
20+
JavaMethodProto::new("run", "()V", Self::run, Default::default()),
21+
],
22+
fields: vec![JavaFieldProto::new("tasks", "Ljava/util/Vector;", Default::default())],
23+
}
24+
}
25+
26+
async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef<Self>, tasks: ClassInstanceRef<Vector>) -> Result<()> {
27+
tracing::debug!("java.util.Timer$TimerThread::<init>({:?})", &this);
28+
29+
let _: () = jvm.invoke_special(&this, "java/lang/Thread", "<init>", "()V", ()).await?;
30+
31+
jvm.put_field(&mut this, "tasks", "Ljava/util/Vector;", tasks).await?;
32+
33+
Ok(())
34+
}
35+
36+
async fn run(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef<Self>) -> Result<()> {
37+
tracing::debug!("java.util.Timer$TimerThread::run({:?})", &this);
38+
39+
let java_tasks = jvm.get_field(&this, "tasks", "Ljava/util/Vector;").await?;
40+
41+
loop {
42+
// TODO: we need to wait for new tasks to arrive
43+
context.sleep(Duration::from_millis(16)).await;
44+
45+
let tasks_size: i32 = jvm.invoke_virtual(&java_tasks, "size", "()I", ()).await?;
46+
if tasks_size == 0 {
47+
continue;
48+
}
49+
50+
// get all tasks. removing from tasks vector to avoid some concurrency issue
51+
let mut tasks = Vec::with_capacity(tasks_size as _);
52+
for _ in 0..tasks_size {
53+
let task = jvm.invoke_virtual(&java_tasks, "remove", "(I)Ljava/lang/Object;", (0,)).await?;
54+
tasks.push(task);
55+
}
56+
57+
// execute tasks
58+
let now = context.now() as i64;
59+
let mut next_tasks = Vec::new();
60+
for mut task in tasks {
61+
let next_execution_time: i64 = jvm.get_field(&task, "nextExecutionTime", "J").await?;
62+
63+
if next_execution_time < now {
64+
let _: () = jvm.invoke_virtual(&task, "run", "()V", ()).await?;
65+
66+
let period: i64 = jvm.get_field(&task, "period", "J").await?;
67+
if period > 0 {
68+
let next_execution_time = now + period;
69+
jvm.put_field(&mut task, "nextExecutionTime", "J", next_execution_time).await?;
70+
next_tasks.push(task);
71+
}
72+
} else {
73+
next_tasks.push(task);
74+
}
75+
}
76+
77+
// add pending tasks
78+
for task in next_tasks {
79+
let _: () = jvm.invoke_virtual(&java_tasks, "addElement", "(Ljava/lang/Object;)V", (task,)).await?;
80+
}
81+
}
82+
}
83+
}

java_runtime/src/loader.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ pub fn get_runtime_class_proto(name: &str) -> Option<RuntimeClassProto> {
7777
crate::classes::java::util::Stack::as_proto(),
7878
crate::classes::java::util::Timer::as_proto(),
7979
crate::classes::java::util::TimerTask::as_proto(),
80+
crate::classes::java::util::TimerThread::as_proto(),
8081
crate::classes::java::util::TimeZone::as_proto(),
8182
crate::classes::java::util::Vector::as_proto(),
8283
crate::classes::java::util::jar::Attributes::as_proto(),

java_runtime/tests/classes/java/util/test_timer.rs

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,25 @@ impl TestClass {
1212
pub fn as_proto() -> RuntimeClassProto {
1313
RuntimeClassProto {
1414
name: "TestClass",
15-
parent_class: Some("java/lang/Object"),
15+
parent_class: Some("java/util/TimerTask"),
1616
interfaces: vec!["java/lang/Runnable"],
1717
methods: vec![
1818
JavaMethodProto::new("<init>", "()V", Self::init, Default::default()),
1919
JavaMethodProto::new("run", "()V", Self::run, Default::default()),
2020
],
21-
fields: vec![JavaFieldProto::new("ran", "Z", Default::default())],
21+
fields: vec![JavaFieldProto::new("runCount", "I", Default::default())],
2222
}
2323
}
2424

25-
async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef<Self>) -> Result<()> {
26-
jvm.put_field(&mut this, "ran", "Z", false).await?;
27-
28-
let _: () = jvm.invoke_special(&this, "java/lang/Object", "<init>", "()V", ()).await?;
25+
async fn init(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef<Self>) -> Result<()> {
26+
let _: () = jvm.invoke_special(&this, "java/util/TimerTask", "<init>", "()V", ()).await?;
2927

3028
Ok(())
3129
}
3230

3331
async fn run(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef<Self>) -> Result<()> {
34-
jvm.put_field(&mut this, "ran", "Z", true).await?;
32+
let count: i32 = jvm.get_field(&this, "runCount", "I").await?;
33+
jvm.put_field(&mut this, "runCount", "I", count + 1).await?;
3534

3635
Ok(())
3736
}
@@ -52,13 +51,50 @@ async fn test_timer() -> Result<()> {
5251

5352
let timer = jvm.new_class("java/util/Timer", "()V", ()).await?;
5453
let _: () = jvm
55-
.invoke_virtual(&timer, "schedule", "(Ljava/util/TimerTask;JJ)V", (test_class.clone(), 0i64, 100i64))
54+
.invoke_virtual(&timer, "schedule", "(Ljava/util/TimerTask;JJ)V", (test_class.clone(), 100i64, 0i64))
55+
.await?;
56+
57+
let _: () = jvm.invoke_static("java/lang/Thread", "sleep", "(J)V", (200i64,)).await?;
58+
let run_count: i32 = jvm.get_field(&test_class, "runCount", "I").await?;
59+
assert_eq!(run_count, 1);
60+
61+
let _: () = jvm
62+
.invoke_virtual(
63+
&timer,
64+
"scheduleAtFixedRate",
65+
"(Ljava/util/TimerTask;JJ)V",
66+
(test_class.clone(), 100i64, 0i64),
67+
)
5668
.await?;
5769

5870
let _: () = jvm.invoke_static("java/lang/Thread", "sleep", "(J)V", (200i64,)).await?;
71+
let run_count: i32 = jvm.get_field(&test_class, "runCount", "I").await?;
72+
assert_eq!(run_count, 2);
73+
74+
Ok(())
75+
}
76+
77+
#[tokio::test]
78+
async fn test_timer_periodic() -> Result<()> {
79+
let runtime = TestRuntime::new(BTreeMap::new());
80+
let jvm = create_test_jvm(runtime.clone()).await?;
81+
82+
let class = Box::new(ClassDefinitionImpl::from_class_proto(
83+
TestClass::as_proto(),
84+
Box::new(runtime.clone()) as Box<_>,
85+
));
86+
jvm.register_class(class, None).await?;
87+
88+
let test_class = jvm.new_class("TestClass", "()V", ()).await?;
89+
90+
let timer = jvm.new_class("java/util/Timer", "()V", ()).await?;
91+
let _: () = jvm
92+
.invoke_virtual(&timer, "schedule", "(Ljava/util/TimerTask;JJ)V", (test_class.clone(), 0i64, 100i64))
93+
.await?;
5994

60-
let ran: bool = jvm.get_field(&test_class, "ran", "Z").await?;
61-
assert!(ran);
95+
let _: () = jvm.invoke_static("java/lang/Thread", "sleep", "(J)V", (500i64,)).await?;
96+
let run_count: i32 = jvm.get_field(&test_class, "runCount", "I").await?;
97+
assert_eq!(run_count, 5);
6298

6399
Ok(())
64100
}

test_utils/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use core::{
66
sync::atomic::{AtomicU64, Ordering},
77
time::Duration,
88
};
9+
use std::time::{SystemTime, UNIX_EPOCH};
910

1011
use jvm::{ClassDefinition, Jvm, Result};
1112
use jvm_rust::{ArrayClassDefinitionImpl, ClassDefinitionImpl};
@@ -53,7 +54,7 @@ impl Runtime for TestRuntime {
5354
}
5455

5556
fn now(&self) -> u64 {
56-
todo!()
57+
SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or(Duration::from_secs(0)).as_millis() as u64
5758
}
5859

5960
fn current_task_id(&self) -> u64 {

0 commit comments

Comments
 (0)