Skip to content

Commit 2a3b623

Browse files
committed
Handle kill as in CRuby
In CRuby, a thread remains internally in "run" state while in the process of being killed, but a killed bit is set for other status-sensitive functions to check. This patch aligns behavior with CRuby.
1 parent cd57e3e commit 2a3b623

File tree

1 file changed

+40
-26
lines changed

1 file changed

+40
-26
lines changed

core/src/main/java/org/jruby/RubyThread.java

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
import org.joni.Matcher;
6767
import org.jruby.anno.JRubyClass;
6868
import org.jruby.anno.JRubyMethod;
69+
import org.jruby.api.Create;
6970
import org.jruby.exceptions.MainExitException;
7071
import org.jruby.exceptions.RaiseException;
7172
import org.jruby.exceptions.ThreadKill;
@@ -196,6 +197,8 @@ public enum Status {
196197
private final static AtomicReferenceFieldUpdater<RubyThread, Status> STATUS =
197198
AtomicReferenceFieldUpdater.newUpdater(RubyThread.class, Status.class, "status");
198199

200+
private volatile boolean killed = false;
201+
199202
/** Mail slot for cross-thread events */
200203
private final Queue<IRubyObject> pendingInterruptQueue = new ConcurrentLinkedQueue<>();
201204

@@ -278,9 +281,9 @@ private void executeInterrupts(ThreadContext context, boolean blockingTiming) {
278281

279282
if (err == UNDEF) {
280283
// no error
281-
} else if (err instanceof RubyFixnum && (((RubyFixnum) err).getLongValue() == 0 ||
282-
((RubyFixnum) err).getLongValue() == 1 ||
283-
((RubyFixnum) err).getLongValue() == 2)) {
284+
} else if (err instanceof RubyFixnum fixErr && (fixErr.getLongValue() == 0 ||
285+
fixErr.getLongValue() == 1 ||
286+
fixErr.getLongValue() == 2)) {
284287
toKill();
285288
} else {
286289
if (getStatus() == Status.SLEEP) {
@@ -318,7 +321,8 @@ private boolean pendingInterruptActive() {
318321

319322
private void toKill() {
320323
pendingInterruptClear();
321-
STATUS.set(this, Status.ABORTING);
324+
killed = true;
325+
STATUS.set(this, Status.RUN);
322326
throwThreadKill();
323327
}
324328

@@ -794,17 +798,13 @@ public void pollThreadEvents(ThreadContext context, boolean blocking) {
794798

795799
// CHECK_INTS
796800
public void pollThreadEvents(ThreadContext context) {
797-
killIfAborting();
798-
799801
if (anyInterrupted()) {
800802
executeInterrupts(context, false);
801803
}
802804
}
803805

804806
// RUBY_VM_CHECK_INTS_BLOCKING
805807
public void blockingThreadPoll(ThreadContext context) {
806-
killIfAborting();
807-
808808
if (pendingInterruptQueue.isEmpty() && !anyInterrupted()) {
809809
return;
810810
}
@@ -814,19 +814,16 @@ public void blockingThreadPoll(ThreadContext context) {
814814
executeInterrupts(context, true);
815815
}
816816

817-
private void killIfAborting() {
818-
if (status == Status.ABORTING) {
819-
// currently aborting, resume abort
820-
throwThreadKill();
821-
}
822-
}
823-
824817
// RUBY_VM_INTERRUPTED_ANY
825818
private boolean anyInterrupted() {
826819
return Thread.interrupted() || (interruptFlag & ~interruptMask) != 0;
827820
}
828821

829-
private static void throwThreadKill() {
822+
/**
823+
* MRI: rb_threadptr_to_kill
824+
*/
825+
private void throwThreadKill() {
826+
killed = true;
830827
throw new ThreadKill();
831828
}
832829

@@ -1373,7 +1370,7 @@ public RubyString inspect(ThreadContext context) {
13731370
result.catString(Integer.toString(line + 1));
13741371
}
13751372
result.cat(' ');
1376-
result.catString(getStatus().toString().toLowerCase());
1373+
result.catString(getStatusName(context));
13771374
result.cat('>');
13781375
return result;
13791376
}
@@ -1647,12 +1644,26 @@ public IRubyObject status() { // not used
16471644

16481645
@JRubyMethod
16491646
public IRubyObject status(ThreadContext context) {
1650-
final Status status = getStatus();
1651-
if (threadImpl.isAlive() && status != Status.DEAD) { // isAlive()
1652-
return context.runtime.getThreadStatus(status);
1647+
if (status == Status.DEAD) {
1648+
return exitingException != null ? context.nil : context.fals;
16531649
}
16541650

1655-
return exitingException != null ? context.nil : context.fals;
1651+
return Create.newString(context, getStatusName(context));
1652+
}
1653+
1654+
private String getStatusName(ThreadContext context) {
1655+
Ruby runtime = context.runtime;
1656+
final Status status = getStatus();
1657+
1658+
switch (status) {
1659+
case RUN:
1660+
if (killed) {
1661+
return "aborting";
1662+
}
1663+
// fall through
1664+
default:
1665+
return status.name().toLowerCase();
1666+
}
16561667
}
16571668

16581669
@JRubyMethod(meta = true, omit = true)
@@ -1952,14 +1963,17 @@ private Status nativeStatus() {
19521963
@JRubyMethod(name = {"kill", "exit", "terminate"})
19531964
public IRubyObject kill() {
19541965
Ruby runtime = getRuntime();
1955-
// need to reexamine this
1956-
RubyThread currentThread = runtime.getCurrentContext().getThread();
19571966

1958-
if (currentThread == runtime.getThreadService().getMainThread()) {
1959-
// rb_exit to hard exit process...not quite right for us
1967+
if (killed == true || status == Status.DEAD) {
1968+
return this;
19601969
}
19611970

1962-
STATUS.set(this, Status.ABORTING);
1971+
ThreadContext context = runtime.getCurrentContext();
1972+
RubyThread currentThread = context.getThread();
1973+
1974+
if (this == runtime.getThreadService().getMainThread()) {
1975+
RubyKernel.exit(context, runtime.getKernel(), Helpers.arrayOf(RubyFixnum.zero(runtime)));
1976+
}
19631977

19641978
return genericKill(runtime, currentThread);
19651979
}

0 commit comments

Comments
 (0)