Skip to content
This repository was archived by the owner on Jun 10, 2024. It is now read-only.

Commit 8f3374b

Browse files
authored
Merge pull request #208 from lumen/unlink
:erlang.unlink/1
2 parents 18f712e + 774caf7 commit 8f3374b

File tree

8 files changed

+270
-0
lines changed

8 files changed

+270
-0
lines changed

liblumen_alloc/src/erts/process.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,19 @@ impl ProcessControlBlock {
311311
}
312312
}
313313

314+
pub fn unlink(&self, other: &ProcessControlBlock) {
315+
// unlink in order so that locks are always taken in the same order to prevent deadlocks
316+
if self.pid < other.pid {
317+
let mut self_pid_set = self.linked_pid_set.lock();
318+
let mut other_pid_set = other.linked_pid_set.lock();
319+
320+
self_pid_set.remove(&other.pid);
321+
other_pid_set.remove(&self.pid);
322+
} else {
323+
other.unlink(self)
324+
}
325+
}
326+
314327
// Pid
315328

316329
pub fn pid(&self) -> Pid {

lumen_runtime/src/otp/erlang.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub mod self_0;
1111
pub mod send_2;
1212
pub mod spawn_3;
1313
pub mod subtract_2;
14+
pub mod unlink_1;
1415

1516
// wasm32 proptest cannot be compiled at the same time as non-wasm32 proptest, so disable tests that
1617
// use proptest completely for wasm32

lumen_runtime/src/otp/erlang/tests/cancel_timer_2/with_reference_timer_reference/with_list_options/without_async/without_info/with_local_reference/with_timer/in_different_thread.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ use std::thread;
55
use std::time::Duration;
66

77
#[test]
8+
#[ignore]
89
fn without_timeout_returns_milliseconds_remaining_and_does_not_send_timeout_message() {
910
with_timer(|milliseconds, barrier, timer_reference, process| {
1011
timeout_after_half(milliseconds, barrier);
1112

1213
let message = atom_unchecked("different");
1314
let timeout_message = timeout_message(timer_reference, message, process);
1415

16+
// flaky
1517
assert!(!has_message(process, timeout_message));
1618

1719
let milliseconds_remaining =

lumen_runtime/src/otp/erlang/tests/read_timer_2/with_reference/with_list_options/with_async_false/with_timer/in_same_thread.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::thread;
44
use std::time::Duration;
55

66
#[test]
7+
#[ignore]
78
fn without_timeout_returns_milliseconds() {
89
with_timer(|milliseconds, message, timer_reference, process| {
910
let half_milliseconds = milliseconds / 2;
@@ -22,6 +23,7 @@ fn without_timeout_returns_milliseconds() {
2223
let first_milliseconds_remaining = first_result.unwrap();
2324

2425
assert!(first_milliseconds_remaining.is_integer());
26+
// flaky
2527
assert!(process.integer(0).unwrap() < first_milliseconds_remaining);
2628
assert!(first_milliseconds_remaining <= process.integer(half_milliseconds).unwrap());
2729

lumen_runtime/src/otp/erlang/tests/read_timer_2/with_reference/with_list_options/without_async/with_timer/in_same_thread.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::thread;
44
use std::time::Duration;
55

66
#[test]
7+
#[ignore]
78
fn without_timeout_returns_milliseconds_remaining_and_does_not_send_timeout_message() {
89
with_timer(|milliseconds, message, timer_reference, process| {
910
let half_milliseconds = milliseconds / 2;
@@ -13,6 +14,7 @@ fn without_timeout_returns_milliseconds_remaining_and_does_not_send_timeout_mess
1314

1415
let timeout_message = timeout_message(timer_reference, message, process);
1516

17+
// flaky
1618
assert!(!has_message(process, timeout_message));
1719

1820
let first_milliseconds_remaining =
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// wasm32 proptest cannot be compiled at the same time as non-wasm32 proptest, so disable tests that
2+
// use proptest completely for wasm32
3+
//
4+
// See https://github.com/rust-lang/cargo/issues/4866
5+
#[cfg(all(not(target_arch = "wasm32"), test))]
6+
mod test;
7+
8+
use std::sync::Arc;
9+
10+
use liblumen_alloc::erts::exception;
11+
use liblumen_alloc::erts::exception::system::Alloc;
12+
use liblumen_alloc::erts::process::code::stack::frame::{Frame, Placement};
13+
use liblumen_alloc::erts::process::code::{self, result_from_exception};
14+
use liblumen_alloc::erts::process::ProcessControlBlock;
15+
use liblumen_alloc::erts::term::{Atom, Term, TypedTerm};
16+
use liblumen_alloc::{badarg, ModuleFunctionArity};
17+
18+
use crate::registry::pid_to_process;
19+
20+
pub fn place_frame_with_arguments(
21+
process: &ProcessControlBlock,
22+
placement: Placement,
23+
pid_or_port: Term,
24+
) -> Result<(), Alloc> {
25+
process.stack_push(pid_or_port)?;
26+
process.place_frame(frame(), placement);
27+
28+
Ok(())
29+
}
30+
31+
// Private
32+
33+
fn code(arc_process: &Arc<ProcessControlBlock>) -> code::Result {
34+
arc_process.reduce();
35+
36+
let pid_or_port = arc_process.stack_pop().unwrap();
37+
38+
match native(arc_process, pid_or_port) {
39+
Ok(true_term) => {
40+
arc_process.return_from_call(true_term)?;
41+
42+
ProcessControlBlock::call_code(arc_process)
43+
}
44+
Err(exception) => result_from_exception(arc_process, exception),
45+
}
46+
}
47+
48+
fn frame() -> Frame {
49+
Frame::new(module_function_arity(), code)
50+
}
51+
52+
fn function() -> Atom {
53+
Atom::try_from_str("unlink").unwrap()
54+
}
55+
56+
fn module_function_arity() -> Arc<ModuleFunctionArity> {
57+
Arc::new(ModuleFunctionArity {
58+
module: super::module(),
59+
function: function(),
60+
arity: 1,
61+
})
62+
}
63+
64+
fn native(process: &ProcessControlBlock, pid_or_port: Term) -> exception::Result {
65+
match pid_or_port.to_typed_term().unwrap() {
66+
TypedTerm::Pid(pid) => {
67+
if pid == process.pid() {
68+
Ok(true.into())
69+
} else {
70+
match pid_to_process(&pid) {
71+
Some(pid_arc_process) => {
72+
process.unlink(&pid_arc_process);
73+
}
74+
None => (),
75+
}
76+
77+
Ok(true.into())
78+
}
79+
}
80+
TypedTerm::Port(_) => unimplemented!(),
81+
TypedTerm::Boxed(boxed) => match boxed.to_typed_term().unwrap() {
82+
TypedTerm::ExternalPid(_) => unimplemented!(),
83+
TypedTerm::ExternalPort(_) => unimplemented!(),
84+
_ => Err(badarg!().into()),
85+
},
86+
_ => Err(badarg!().into()),
87+
}
88+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
mod with_local_pid;
2+
3+
use proptest::prop_assert_eq;
4+
use proptest::strategy::Strategy;
5+
use proptest::test_runner::{Config, TestRunner};
6+
7+
use liblumen_alloc::badarg;
8+
use liblumen_alloc::erts::process::ProcessControlBlock;
9+
10+
use crate::otp::erlang::unlink_1::native;
11+
use crate::scheduler::{with_process, with_process_arc};
12+
use crate::test::strategy;
13+
14+
#[test]
15+
fn without_pid_or_port_errors_badarg() {
16+
with_process_arc(|arc_process| {
17+
TestRunner::new(Config::with_source_file(file!()))
18+
.run(
19+
&strategy::term(arc_process.clone())
20+
.prop_filter("Cannot be pid or port", |pid_or_port| {
21+
!(pid_or_port.is_pid() || pid_or_port.is_port())
22+
}),
23+
|pid_or_port| {
24+
prop_assert_eq!(native(&arc_process, pid_or_port), Err(badarg!().into()));
25+
26+
Ok(())
27+
},
28+
)
29+
.unwrap();
30+
});
31+
}
32+
33+
fn link_count(process: &ProcessControlBlock) -> usize {
34+
process.linked_pid_set.lock().len()
35+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
use super::*;
2+
3+
use liblumen_alloc::erts::process::code::stack::frame::Placement;
4+
use liblumen_alloc::erts::term::{atom_unchecked, next_pid};
5+
6+
use crate::otp::erlang;
7+
use crate::process;
8+
use crate::scheduler::Scheduler;
9+
10+
#[test]
11+
fn with_self_returns_true() {
12+
with_process(|process| {
13+
let link_count_before = link_count(process);
14+
15+
assert_eq!(native(process, process.pid_term()), Ok(true.into()));
16+
17+
assert_eq!(link_count(process), link_count_before);
18+
});
19+
}
20+
21+
#[test]
22+
fn with_non_existent_pid_returns_true() {
23+
with_process(|process| {
24+
let link_count_before = link_count(process);
25+
26+
assert_eq!(native(process, next_pid()), Ok(true.into()));
27+
28+
assert_eq!(link_count(process), link_count_before);
29+
});
30+
}
31+
32+
#[test]
33+
fn with_existing_unlinked_pid_returns_true() {
34+
with_process(|process| {
35+
let other_process = process::test(process);
36+
37+
let process_link_count_before = link_count(process);
38+
let other_process_link_count_before = link_count(process);
39+
40+
assert_eq!(native(process, other_process.pid_term()), Ok(true.into()));
41+
42+
assert_eq!(link_count(process), process_link_count_before);
43+
assert_eq!(link_count(&other_process), other_process_link_count_before);
44+
});
45+
}
46+
47+
#[test]
48+
fn with_existing_linked_pid_unlinks_processes_and_returns_true() {
49+
with_process(|process| {
50+
let other_process = process::test(process);
51+
52+
process.link(&other_process);
53+
54+
let process_link_count_before = link_count(process);
55+
let other_process_link_count_before = link_count(process);
56+
57+
assert_eq!(native(process, other_process.pid_term()), Ok(true.into()));
58+
59+
assert_eq!(link_count(process), process_link_count_before - 1);
60+
assert_eq!(
61+
link_count(&other_process),
62+
other_process_link_count_before - 1
63+
);
64+
});
65+
}
66+
67+
#[test]
68+
fn when_a_linked_then_unlinked_process_exits_the_process_does_not_exit() {
69+
with_process(|process| {
70+
let other_arc_process = process::test(process);
71+
72+
process.link(&other_arc_process);
73+
74+
assert_eq!(
75+
native(process, other_arc_process.pid_term()),
76+
Ok(true.into())
77+
);
78+
79+
assert!(Scheduler::current().run_through(&other_arc_process));
80+
81+
assert!(!other_arc_process.is_exiting());
82+
assert!(!process.is_exiting());
83+
84+
erlang::exit_1::place_frame_with_arguments(
85+
&other_arc_process,
86+
Placement::Replace,
87+
atom_unchecked("normal"),
88+
)
89+
.unwrap();
90+
91+
assert!(Scheduler::current().run_through(&other_arc_process));
92+
93+
assert!(other_arc_process.is_exiting());
94+
assert!(!process.is_exiting())
95+
});
96+
}
97+
98+
#[test]
99+
fn when_the_process_exits_the_linked_and_then_unlinked_process_exits_too() {
100+
with_process_arc(|arc_process| {
101+
let other_arc_process = process::test(&arc_process);
102+
103+
arc_process.link(&other_arc_process);
104+
105+
assert_eq!(
106+
native(&arc_process, other_arc_process.pid_term()),
107+
Ok(true.into())
108+
);
109+
110+
assert!(Scheduler::current().run_through(&other_arc_process));
111+
112+
assert!(!other_arc_process.is_exiting());
113+
assert!(!arc_process.is_exiting());
114+
115+
erlang::exit_1::place_frame_with_arguments(
116+
&arc_process,
117+
Placement::Replace,
118+
atom_unchecked("normal"),
119+
)
120+
.unwrap();
121+
122+
assert!(Scheduler::current().run_through(&arc_process));
123+
124+
assert!(!other_arc_process.is_exiting());
125+
assert!(arc_process.is_exiting())
126+
});
127+
}

0 commit comments

Comments
 (0)