Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid race condition in GC_Example test #10665

Merged
merged 8 commits into from
Jul 29, 2024
12 changes: 5 additions & 7 deletions build/build/src/engine/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -661,12 +661,10 @@ pub async fn runner_sanity_test(
.args(["--run", repo_root.test.join("Base_Tests").as_str()])
.set_env_opt(ENSO_JAVA, enso_java)?
.set_env(ENSO_DATA_DIRECTORY, engine_package)?
.run_stdout()
.await?;
ensure!(
test_base.contains("0 tests failed."),
"All tests shall succeed. Output:\n{test_base}",
);
.run_ok()
.await;
test_base
} else {
Ok(())
}
Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -177,29 +177,34 @@ private final class ProcessItems extends ThreadLocalAction implements Runnable {
*/
@Override
protected void perform(ThreadLocalAction.Access access) {
var isMyThreadChoosen = false;
for (; ; ) {
Item[] toProcess;
synchronized (pendingItems) {
request.cancel(false);
if (!isMyThreadChoosen) {
if (request == null || request.isCancelled()) {
// some thread is already handing the request
JaroslavTulach marked this conversation as resolved.
Show resolved Hide resolved
return;
} else {
// I am choosen and I will loop and process pendingItems
// until they are available
isMyThreadChoosen = true;
// signal others this request has choosen thread
request.cancel(false);
}
}
if (pendingItems.isEmpty()) {
// nothing to process,
// signal request is finished
// nothing to process anymore,
// signal request is finished and new one shall be scheduled
request = null;
return;
}
toProcess = pendingItems.toArray(Item[]::new);
// mark as being processed
pendingItems.set(0, null);
pendingItems.clear();
}
try {
for (var it : toProcess) {
it.finalizeNow(context);
removeFromItems(it);
}
} finally {
synchronized (pendingItems) {
pendingItems.subList(0, toProcess.length).clear();
}
for (var it : toProcess) {
it.finalizeNow(context);
removeFromItems(it);
}
}
}
Expand Down
48 changes: 32 additions & 16 deletions test/Base_Tests/src/Runtime/GC_Example.enso
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from Standard.Base import all
import Standard.Base.Errors.Illegal_State.Illegal_State
import Standard.Base.Runtime.Managed_Resource.Managed_Resource
import Standard.Base.Runtime.Ref.Ref

Expand All @@ -15,27 +16,42 @@ type My_Resource

JaroslavTulach marked this conversation as resolved.
Show resolved Hide resolved
close_resource resource = resource.close

repeat_cleanup_until_done counter =
go i =
if counter.get == 0 then Nothing else
if i % 100 == 0 then
IO.println "Still "+counter.get.to_text+" resources to clean up..."
Runtime.gc
@Tail_Call go i+1
go 1
repeat_cleanup_until_done counter println =
deadline = Date_Time.now + (Duration.new minutes=5)
while_counter_not_zero i = if counter.get != 0 then
if i % 10 == 0 then
println "Still "+counter.get.to_text+" resources to clean up..."

perform_test n:Integer println =
Runtime.gc

deadline_not_reached at = if at < deadline then True else
message = "Timeout has been reached, but there are "+counter.get.to_text+" resources remaining that were not cleaned up."
println message
False

if deadline_not_reached Date_Time.now then
@Tail_Call while_counter_not_zero i+1

while_counter_not_zero 1

perform_test n:Integer println =
resource_holder = Ref.new Nothing
counter = Ref.new 0
println "Allocating "+n.to_text+" resources..."
0.up_to n . each _->
My_Resource.allocate counter

println "Cleaning up..."
repeat_cleanup_until_done counter
println "All cleaned up! Remaining: "+counter.get.to_text
println "Allocating "+n.to_text+" resources..."
allocate_resources ref =
all = 0.up_to n . map _->
My_Resource.allocate counter
ref.put all
allocate_resources resource_holder

println "Cleaning up "+resource_holder.get.length.to_text+" resources"
# We replace the vector with Nothing to make the resources inaccessible and schedule them for GC
resource_holder.put Nothing

repeat_cleanup_until_done counter println
println "Cleaning finished! Remaining: "+counter.get.to_text
counter.get

main n=1000000 =
perform_test n IO.println

12 changes: 9 additions & 3 deletions test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,15 @@ add_specs suite_builder = suite_builder.group "Managed_Resource" group_builder->
r_3 = Panic.recover Any <| Managed_Resource.bracket 42 (_-> Nothing) (_-> Panic.throw "action")
r_3.catch . should_equal "action"

group_builder.specify "allocate lots of resources at once" pending=(if Environment.get "CI" . is_nothing . not then "GC Example is marked as pending as it was causing problems on the CI.") <|
remaining = GC_Example.perform_test 100000 (_->Nothing)
remaining . should_equal 0
group_builder.specify "allocate lots of resources at once" <|
messages = Vector.build builder->
builder.append '\n'
remaining = GC_Example.perform_test 100000 builder.append
if remaining == 0 then
builder.append "OK"

if messages.last != "OK" then
Test.fail (messages.join '\n')

main filter=Nothing =
suite = Test.build suite_builder->
Expand Down
Loading