001: /***** BEGIN LICENSE BLOCK *****
002: * Version: CPL 1.0/GPL 2.0/LGPL 2.1
003: *
004: * The contents of this file are subject to the Common Public
005: * License Version 1.0 (the "License"); you may not use this file
006: * except in compliance with the License. You may obtain a copy of
007: * the License at http://www.eclipse.org/legal/cpl-v10.html
008: *
009: * Software distributed under the License is distributed on an "AS
010: * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
011: * implied. See the License for the specific language governing
012: * rights and limitations under the License.
013: *
014: * Copyright (C) 2002 Jason Voegele <jason@jvoegele.com>
015: * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
016: * Copyright (C) 2002-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
017: * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
018: * Copyright (C) 2004-2005 Charles O Nutter <headius@headius.com>
019: * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
020: *
021: * Alternatively, the contents of this file may be used under the terms of
022: * either of the GNU General Public License Version 2 or later (the "GPL"),
023: * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
024: * in which case the provisions of the GPL or the LGPL are applicable instead
025: * of those above. If you wish to allow use of your version of this file only
026: * under the terms of either the GPL or the LGPL, and not to allow others to
027: * use your version of this file under the terms of the CPL, indicate your
028: * decision by deleting the provisions above and replace them with the notice
029: * and other provisions required by the GPL or the LGPL. If you do not delete
030: * the provisions above, a recipient may use your version of this file under
031: * the terms of any one of the CPL, the GPL or the LGPL.
032: ***** END LICENSE BLOCK *****/package org.jruby;
033:
034: import java.util.HashMap;
035: import java.util.Map;
036: import org.jruby.exceptions.RaiseException;
037: import org.jruby.exceptions.ThreadKill;
038: import org.jruby.internal.runtime.FutureThread;
039: import org.jruby.internal.runtime.NativeThread;
040: import org.jruby.internal.runtime.RubyNativeThread;
041: import org.jruby.internal.runtime.RubyRunnable;
042: import org.jruby.internal.runtime.ThreadLike;
043: import org.jruby.internal.runtime.ThreadService;
044: import org.jruby.runtime.Block;
045: import org.jruby.runtime.CallbackFactory;
046: import org.jruby.runtime.ObjectAllocator;
047: import org.jruby.runtime.ThreadContext;
048: import org.jruby.runtime.builtin.IRubyObject;
049:
050: import edu.emory.mathcs.backport.java.util.concurrent.ExecutionException;
051: import edu.emory.mathcs.backport.java.util.concurrent.TimeoutException;
052: import edu.emory.mathcs.backport.java.util.concurrent.locks.ReentrantLock;
053: import org.jruby.runtime.Arity;
054: import org.jruby.runtime.ObjectMarshal;
055:
056: /**
057: * Implementation of Ruby's <code>Thread</code> class. Each Ruby thread is
058: * mapped to an underlying Java Virtual Machine thread.
059: * <p>
060: * Thread encapsulates the behavior of a thread of execution, including the main
061: * thread of the Ruby script. In the descriptions that follow, the parameter
062: * <code>aSymbol</code> refers to a symbol, which is either a quoted string or a
063: * <code>Symbol</code> (such as <code>:name</code>).
064: *
065: * Note: For CVS history, see ThreadClass.java.
066: */
067: public class RubyThread extends RubyObject {
068: private ThreadLike threadImpl;
069: private Map threadLocalVariables = new HashMap();
070: private boolean abortOnException;
071: private IRubyObject finalResult;
072: private RaiseException exitingException;
073: private IRubyObject receivedException;
074: private RubyThreadGroup threadGroup;
075:
076: private ThreadService threadService;
077: private volatile boolean isStopped = false;
078: public Object stopLock = new Object();
079:
080: private volatile boolean killed = false;
081: public Object killLock = new Object();
082:
083: public final ReentrantLock lock = new ReentrantLock();
084:
085: private static boolean USE_POOLING;
086:
087: private static final boolean DEBUG = false;
088:
089: static {
090: if (Ruby.isSecurityRestricted())
091: USE_POOLING = false;
092: else
093: USE_POOLING = Boolean.getBoolean("jruby.thread.pooling");
094: }
095:
096: public static RubyClass createThreadClass(Ruby runtime) {
097: // FIXME: In order for Thread to play well with the standard 'new' behavior,
098: // it must provide an allocator that can create empty object instances which
099: // initialize then fills with appropriate data.
100: RubyClass threadClass = runtime
101: .defineClass("Thread", runtime.getObject(),
102: ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
103: CallbackFactory callbackFactory = runtime
104: .callbackFactory(RubyThread.class);
105:
106: threadClass.defineFastMethod("[]", callbackFactory
107: .getFastMethod("aref", RubyKernel.IRUBY_OBJECT));
108: threadClass.defineFastMethod("[]=", callbackFactory
109: .getFastMethod("aset", RubyKernel.IRUBY_OBJECT,
110: RubyKernel.IRUBY_OBJECT));
111: threadClass.defineFastMethod("abort_on_exception",
112: callbackFactory.getFastMethod("abort_on_exception"));
113: threadClass.defineFastMethod("abort_on_exception=",
114: callbackFactory.getFastMethod("abort_on_exception_set",
115: RubyKernel.IRUBY_OBJECT));
116: threadClass.defineFastMethod("alive?", callbackFactory
117: .getFastMethod("is_alive"));
118: threadClass.defineFastMethod("group", callbackFactory
119: .getFastMethod("group"));
120: threadClass.defineFastMethod("join", callbackFactory
121: .getFastOptMethod("join"));
122: threadClass.defineFastMethod("value", callbackFactory
123: .getFastMethod("value"));
124: threadClass.defineMethod("initialize", callbackFactory
125: .getOptMethod("initialize"));
126: threadClass.defineFastMethod("inspect", callbackFactory
127: .getFastMethod("inspect"));
128: threadClass.defineFastMethod("key?", callbackFactory
129: .getFastMethod("has_key", RubyKernel.IRUBY_OBJECT));
130: threadClass.defineFastMethod("keys", callbackFactory
131: .getFastMethod("keys"));
132: threadClass.defineFastMethod("priority", callbackFactory
133: .getFastMethod("priority"));
134: threadClass
135: .defineFastMethod("priority=", callbackFactory
136: .getFastMethod("priority_set",
137: RubyKernel.IRUBY_OBJECT));
138: threadClass.defineMethod("raise", callbackFactory
139: .getOptMethod("raise"));
140: //threadClass.defineFastMethod("raise", callbackFactory.getFastMethod("raise", RubyKernel.IRUBY_OBJECT));
141: threadClass.defineFastMethod("run", callbackFactory
142: .getFastMethod("run"));
143: threadClass.defineFastMethod("status", callbackFactory
144: .getFastMethod("status"));
145: threadClass.defineFastMethod("stop?", callbackFactory
146: .getFastMethod("isStopped"));
147: threadClass.defineFastMethod("wakeup", callbackFactory
148: .getFastMethod("wakeup"));
149: // threadClass.defineMethod("value",
150: // callbackFactory.getMethod("value"));
151: threadClass.defineFastMethod("kill", callbackFactory
152: .getFastMethod("kill"));
153: threadClass.defineFastMethod("exit", callbackFactory
154: .getFastMethod("exit"));
155:
156: threadClass.getMetaClass().defineFastMethod("current",
157: callbackFactory.getFastSingletonMethod("current"));
158: threadClass.getMetaClass().defineMethod("fork",
159: callbackFactory.getOptSingletonMethod("newInstance"));
160: threadClass.getMetaClass().defineMethod("new",
161: callbackFactory.getOptSingletonMethod("newInstance"));
162: threadClass.getMetaClass().defineFastMethod("list",
163: callbackFactory.getFastSingletonMethod("list"));
164: threadClass.getMetaClass().defineFastMethod("pass",
165: callbackFactory.getFastSingletonMethod("pass"));
166: threadClass.getMetaClass().defineMethod("start",
167: callbackFactory.getOptSingletonMethod("start"));
168: threadClass.getMetaClass().defineFastMethod(
169: "critical=",
170: callbackFactory.getFastSingletonMethod("critical_set",
171: RubyBoolean.class));
172: threadClass.getMetaClass().defineFastMethod("critical",
173: callbackFactory.getFastSingletonMethod("critical"));
174: threadClass.getMetaClass().defineFastMethod("stop",
175: callbackFactory.getFastSingletonMethod("stop"));
176: threadClass.getMetaClass().defineMethod(
177: "kill",
178: callbackFactory.getSingletonMethod("s_kill",
179: RubyThread.class));
180: threadClass.getMetaClass().defineMethod("exit",
181: callbackFactory.getSingletonMethod("s_exit"));
182: threadClass.getMetaClass().defineFastMethod(
183: "abort_on_exception",
184: callbackFactory
185: .getFastSingletonMethod("abort_on_exception"));
186: threadClass.getMetaClass().defineFastMethod(
187: "abort_on_exception=",
188: callbackFactory.getFastSingletonMethod(
189: "abort_on_exception_set",
190: RubyKernel.IRUBY_OBJECT));
191:
192: RubyThread rubyThread = new RubyThread(runtime, threadClass);
193: // TODO: need to isolate the "current" thread from class creation
194: rubyThread.threadImpl = new NativeThread(rubyThread, Thread
195: .currentThread());
196: runtime.getThreadService().setMainThread(rubyThread);
197:
198: threadClass.getMetaClass().defineFastMethod("main",
199: callbackFactory.getFastSingletonMethod("main"));
200:
201: threadClass.setMarshal(ObjectMarshal.NOT_MARSHALABLE_MARSHAL);
202:
203: return threadClass;
204: }
205:
206: /**
207: * <code>Thread.new</code>
208: * <p>
209: * Thread.new( <i>[ arg ]*</i> ) {| args | block } -> aThread
210: * <p>
211: * Creates a new thread to execute the instructions given in block, and
212: * begins running it. Any arguments passed to Thread.new are passed into the
213: * block.
214: * <pre>
215: * x = Thread.new { sleep .1; print "x"; print "y"; print "z" }
216: * a = Thread.new { print "a"; print "b"; sleep .2; print "c" }
217: * x.join # Let the threads finish before
218: * a.join # main thread exits...
219: * </pre>
220: * <i>produces:</i> abxyzc
221: */
222: public static IRubyObject newInstance(IRubyObject recv,
223: IRubyObject[] args, Block block) {
224: return startThread(recv, args, true, block);
225: }
226:
227: /**
228: * Basically the same as Thread.new . However, if class Thread is
229: * subclassed, then calling start in that subclass will not invoke the
230: * subclass's initialize method.
231: */
232: public static RubyThread start(IRubyObject recv,
233: IRubyObject[] args, Block block) {
234: return startThread(recv, args, false, block);
235: }
236:
237: public static RubyThread adopt(IRubyObject recv, Thread t) {
238: return adoptThread(recv, t, Block.NULL_BLOCK);
239: }
240:
241: private static RubyThread adoptThread(final IRubyObject recv,
242: Thread t, Block block) {
243: final Ruby runtime = recv.getRuntime();
244: final RubyThread rubyThread = new RubyThread(runtime,
245: (RubyClass) recv);
246:
247: rubyThread.threadImpl = new NativeThread(rubyThread, t);
248: runtime.getThreadService().registerNewThread(rubyThread);
249:
250: runtime.getCurrentContext().preAdoptThread();
251:
252: // adoptThread does not call init, since init expects a block to be passed in
253: if (USE_POOLING) {
254: rubyThread.threadImpl = new FutureThread(rubyThread,
255: new RubyRunnable(rubyThread,
256: IRubyObject.NULL_ARRAY, block));
257: } else {
258: rubyThread.threadImpl = new NativeThread(rubyThread,
259: new RubyNativeThread(rubyThread,
260: IRubyObject.NULL_ARRAY, block));
261: }
262: rubyThread.threadImpl.start();
263:
264: return rubyThread;
265: }
266:
267: public IRubyObject initialize(IRubyObject[] args, Block block) {
268: if (!block.isGiven())
269: throw getRuntime().newThreadError(
270: "must be called with a block");
271:
272: if (USE_POOLING) {
273: threadImpl = new FutureThread(this , new RubyRunnable(this ,
274: args, block));
275: } else {
276: threadImpl = new NativeThread(this , new RubyNativeThread(
277: this , args, block));
278: }
279: threadImpl.start();
280:
281: return this ;
282: }
283:
284: private static RubyThread startThread(final IRubyObject recv,
285: final IRubyObject[] args, boolean callInit, Block block) {
286: RubyThread rubyThread = new RubyThread(recv.getRuntime(),
287: (RubyClass) recv);
288:
289: if (callInit) {
290: rubyThread.callInit(args, block);
291: } else {
292: // for Thread::start, which does not call the subclass's initialize
293: rubyThread.initialize(args, block);
294: }
295:
296: return rubyThread;
297: }
298:
299: private void ensureCurrent() {
300: if (this != getRuntime().getCurrentContext().getThread()) {
301: throw new RuntimeException(
302: "internal thread method called from another thread");
303: }
304: }
305:
306: private void ensureNotCurrent() {
307: if (this == getRuntime().getCurrentContext().getThread()) {
308: throw new RuntimeException(
309: "internal thread method called from another thread");
310: }
311: }
312:
313: public void cleanTerminate(IRubyObject result) {
314: finalResult = result;
315: isStopped = true;
316: }
317:
318: public void pollThreadEvents() {
319: // check for criticalization *before* locking ourselves
320: threadService.waitForCritical();
321:
322: ensureCurrent();
323:
324: if (DEBUG)
325: System.out.println("thread " + Thread.currentThread()
326: + " before");
327: if (killed)
328: throw new ThreadKill();
329:
330: if (DEBUG)
331: System.out.println("thread " + Thread.currentThread()
332: + " after");
333: if (receivedException != null) {
334: // clear this so we don't keep re-throwing
335: IRubyObject raiseException = receivedException;
336: receivedException = null;
337: RubyModule kernelModule = getRuntime().getModule("Kernel");
338: if (DEBUG)
339: System.out.println("thread " + Thread.currentThread()
340: + " before propagating exception: " + killed);
341: kernelModule.callMethod(getRuntime().getCurrentContext(),
342: "raise", raiseException);
343: }
344: }
345:
346: private RubyThread(Ruby runtime, RubyClass type) {
347: super (runtime, type);
348: this .threadService = runtime.getThreadService();
349: // set to default thread group
350: RubyThreadGroup defaultThreadGroup = (RubyThreadGroup) runtime
351: .getClass("ThreadGroup").getConstant("Default");
352: defaultThreadGroup.add(this , Block.NULL_BLOCK);
353: finalResult = runtime.getNil();
354: }
355:
356: /**
357: * Returns the status of the global ``abort on exception'' condition. The
358: * default is false. When set to true, will cause all threads to abort (the
359: * process will exit(0)) if an exception is raised in any thread. See also
360: * Thread.abort_on_exception= .
361: */
362: public static RubyBoolean abort_on_exception(IRubyObject recv) {
363: Ruby runtime = recv.getRuntime();
364: return runtime.isGlobalAbortOnExceptionEnabled() ? recv
365: .getRuntime().getTrue() : recv.getRuntime().getFalse();
366: }
367:
368: public static IRubyObject abort_on_exception_set(IRubyObject recv,
369: IRubyObject value) {
370: recv.getRuntime().setGlobalAbortOnExceptionEnabled(
371: value.isTrue());
372: return value;
373: }
374:
375: public static RubyThread current(IRubyObject recv) {
376: return recv.getRuntime().getCurrentContext().getThread();
377: }
378:
379: public static RubyThread main(IRubyObject recv) {
380: return recv.getRuntime().getThreadService().getMainThread();
381: }
382:
383: public static IRubyObject pass(IRubyObject recv) {
384: Ruby runtime = recv.getRuntime();
385: ThreadService ts = runtime.getThreadService();
386: boolean critical = ts.getCritical();
387:
388: ts.setCritical(false);
389:
390: Thread.yield();
391:
392: ts.setCritical(critical);
393:
394: return recv.getRuntime().getNil();
395: }
396:
397: public static RubyArray list(IRubyObject recv) {
398: RubyThread[] activeThreads = recv.getRuntime()
399: .getThreadService().getActiveRubyThreads();
400:
401: return recv.getRuntime().newArrayNoCopy(activeThreads);
402: }
403:
404: private IRubyObject getSymbolKey(IRubyObject originalKey) {
405: if (originalKey instanceof RubySymbol) {
406: return originalKey;
407: } else if (originalKey instanceof RubyString) {
408: return RubySymbol.newSymbol(getRuntime(), originalKey
409: .asSymbol());
410: } else if (originalKey instanceof RubyFixnum) {
411: getRuntime().getWarnings().warn(
412: "Do not use Fixnums as Symbols");
413: throw getRuntime().newArgumentError(
414: originalKey + " is not a symbol");
415: } else {
416: throw getRuntime().newArgumentError(
417: originalKey + " is not a symbol");
418: }
419: }
420:
421: public IRubyObject aref(IRubyObject key) {
422: key = getSymbolKey(key);
423:
424: if (!threadLocalVariables.containsKey(key)) {
425: return getRuntime().getNil();
426: }
427: return (IRubyObject) threadLocalVariables.get(key);
428: }
429:
430: public IRubyObject aset(IRubyObject key, IRubyObject value) {
431: key = getSymbolKey(key);
432:
433: threadLocalVariables.put(key, value);
434: return value;
435: }
436:
437: public RubyBoolean abort_on_exception() {
438: return abortOnException ? getRuntime().getTrue() : getRuntime()
439: .getFalse();
440: }
441:
442: public IRubyObject abort_on_exception_set(IRubyObject val) {
443: abortOnException = val.isTrue();
444: return val;
445: }
446:
447: public RubyBoolean is_alive() {
448: return threadImpl.isAlive() ? getRuntime().getTrue()
449: : getRuntime().getFalse();
450: }
451:
452: public RubyThread join(IRubyObject[] args) {
453: long timeoutMillis = 0;
454: if (args.length > 0) {
455: if (args.length > 1) {
456: throw getRuntime().newArgumentError(args.length, 1);
457: }
458: // MRI behavior: value given in seconds; converted to Float; less
459: // than or equal to zero returns immediately; returns nil
460: timeoutMillis = (long) (1000.0D * args[0].convertToFloat()
461: .getValue());
462: if (timeoutMillis <= 0) {
463: return null;
464: }
465: }
466: if (isCurrent()) {
467: throw getRuntime().newThreadError(
468: "thread tried to join itself");
469: }
470: try {
471: if (threadService.getCritical()) {
472: threadImpl.interrupt(); // break target thread out of critical
473: }
474: threadImpl.join(timeoutMillis);
475: } catch (InterruptedException iExcptn) {
476: assert false : iExcptn;
477: } catch (TimeoutException iExcptn) {
478: assert false : iExcptn;
479: } catch (ExecutionException iExcptn) {
480: assert false : iExcptn;
481: }
482: if (exitingException != null) {
483: throw exitingException;
484: }
485: return null;
486: }
487:
488: public IRubyObject value() {
489: join(new IRubyObject[0]);
490: synchronized (this ) {
491: return finalResult;
492: }
493: }
494:
495: public IRubyObject group() {
496: if (threadGroup == null) {
497: return getRuntime().getNil();
498: }
499:
500: return threadGroup;
501: }
502:
503: void setThreadGroup(RubyThreadGroup rubyThreadGroup) {
504: threadGroup = rubyThreadGroup;
505: }
506:
507: public IRubyObject inspect() {
508: // FIXME: There's some code duplication here with RubyObject#inspect
509: StringBuffer part = new StringBuffer();
510: String cname = getMetaClass().getRealClass().getName();
511: part.append("#<").append(cname).append(":0x");
512: part.append(Integer.toHexString(System.identityHashCode(this )));
513:
514: if (threadImpl.isAlive()) {
515: if (isStopped) {
516: part.append(getRuntime().newString(" sleep"));
517: } else if (killed) {
518: part.append(getRuntime().newString(" aborting"));
519: } else {
520: part.append(getRuntime().newString(" run"));
521: }
522: } else {
523: part.append(" dead");
524: }
525:
526: part.append(">");
527: return getRuntime().newString(part.toString());
528: }
529:
530: public RubyBoolean has_key(IRubyObject key) {
531: key = getSymbolKey(key);
532:
533: return getRuntime().newBoolean(
534: threadLocalVariables.containsKey(key));
535: }
536:
537: public RubyArray keys() {
538: IRubyObject[] keys = new IRubyObject[threadLocalVariables
539: .size()];
540:
541: return RubyArray.newArrayNoCopy(getRuntime(),
542: (IRubyObject[]) threadLocalVariables.keySet().toArray(
543: keys));
544: }
545:
546: public static IRubyObject critical_set(IRubyObject receiver,
547: RubyBoolean value) {
548: receiver.getRuntime().getThreadService().setCritical(
549: value.isTrue());
550:
551: return value;
552: }
553:
554: public static IRubyObject critical(IRubyObject receiver) {
555: return receiver.getRuntime().newBoolean(
556: receiver.getRuntime().getThreadService().getCritical());
557: }
558:
559: public static IRubyObject stop(IRubyObject receiver) {
560: RubyThread rubyThread = receiver.getRuntime()
561: .getThreadService().getCurrentContext().getThread();
562: Object stopLock = rubyThread.stopLock;
563:
564: synchronized (stopLock) {
565: try {
566: rubyThread.isStopped = true;
567: // attempt to decriticalize all if we're the critical thread
568: receiver.getRuntime().getThreadService().setCritical(
569: false);
570:
571: stopLock.wait();
572: } catch (InterruptedException ie) {
573: // ignore, continue;
574: }
575: rubyThread.isStopped = false;
576: }
577:
578: return receiver.getRuntime().getNil();
579: }
580:
581: public static IRubyObject s_kill(IRubyObject receiver,
582: RubyThread rubyThread, Block block) {
583: return rubyThread.kill();
584: }
585:
586: public static IRubyObject s_exit(IRubyObject receiver, Block block) {
587: RubyThread rubyThread = receiver.getRuntime()
588: .getThreadService().getCurrentContext().getThread();
589:
590: rubyThread.killed = true;
591: // attempt to decriticalize all if we're the critical thread
592: receiver.getRuntime().getThreadService().setCritical(false);
593:
594: throw new ThreadKill();
595: }
596:
597: public RubyBoolean isStopped() {
598: // not valid for "dead" state
599: return getRuntime().newBoolean(isStopped);
600: }
601:
602: public RubyThread wakeup() {
603: synchronized (stopLock) {
604: stopLock.notifyAll();
605: }
606:
607: return this ;
608: }
609:
610: public RubyFixnum priority() {
611: return getRuntime().newFixnum(threadImpl.getPriority());
612: }
613:
614: public IRubyObject priority_set(IRubyObject priority) {
615: // FIXME: This should probably do some translation from Ruby priority levels to Java priority levels (until we have green threads)
616: int iPriority = RubyNumeric.fix2int(priority);
617:
618: if (iPriority < Thread.MIN_PRIORITY) {
619: iPriority = Thread.MIN_PRIORITY;
620: } else if (iPriority > Thread.MAX_PRIORITY) {
621: iPriority = Thread.MAX_PRIORITY;
622: }
623:
624: threadImpl.setPriority(iPriority);
625: return priority;
626: }
627:
628: public IRubyObject raise(IRubyObject[] args, Block block) {
629: ensureNotCurrent();
630: Ruby runtime = getRuntime();
631:
632: if (DEBUG)
633: System.out.println("thread " + Thread.currentThread()
634: + " before raising");
635: RubyThread currentThread = getRuntime().getCurrentContext()
636: .getThread();
637: try {
638: while (!(currentThread.lock.tryLock() && this .lock
639: .tryLock())) {
640: if (currentThread.lock.isHeldByCurrentThread())
641: currentThread.lock.unlock();
642: }
643:
644: currentThread.pollThreadEvents();
645: if (DEBUG)
646: System.out.println("thread " + Thread.currentThread()
647: + " raising");
648: receivedException = prepareRaiseException(runtime, args,
649: block);
650:
651: // interrupt the target thread in case it's blocking or waiting
652: threadImpl.interrupt();
653: } finally {
654: if (currentThread.lock.isHeldByCurrentThread())
655: currentThread.lock.unlock();
656: if (this .lock.isHeldByCurrentThread())
657: this .lock.unlock();
658: }
659:
660: return this ;
661: }
662:
663: private IRubyObject prepareRaiseException(Ruby runtime,
664: IRubyObject[] args, Block block) {
665: Arity.checkArgumentCount(getRuntime(), args, 0, 3);
666:
667: if (args.length == 0) {
668: IRubyObject lastException = runtime.getGlobalVariables()
669: .get("$!");
670: if (lastException.isNil()) {
671: return new RaiseException(runtime, runtime
672: .getClass("RuntimeError"), "", false)
673: .getException();
674: }
675: return lastException;
676: }
677:
678: IRubyObject exception;
679: ThreadContext context = getRuntime().getCurrentContext();
680:
681: if (args.length == 1) {
682: if (args[0] instanceof RubyString) {
683: return runtime.getClass("RuntimeError").newInstance(
684: args, block);
685: }
686:
687: if (!args[0].respondsTo("exception")) {
688: return runtime.newTypeError(
689: "exception class/object expected")
690: .getException();
691: }
692: exception = args[0].callMethod(context, "exception");
693: } else {
694: if (!args[0].respondsTo("exception")) {
695: return runtime.newTypeError(
696: "exception class/object expected")
697: .getException();
698: }
699:
700: exception = args[0].callMethod(context, "exception",
701: args[1]);
702: }
703:
704: if (!exception.isKindOf(runtime.getClass("Exception"))) {
705: return runtime.newTypeError("exception object expected")
706: .getException();
707: }
708:
709: if (args.length == 3) {
710: ((RubyException) exception).set_backtrace(args[2]);
711: }
712:
713: return exception;
714: }
715:
716: public IRubyObject run() {
717: // if stopped, unstop
718: synchronized (stopLock) {
719: if (isStopped) {
720: isStopped = false;
721: stopLock.notifyAll();
722: }
723: }
724:
725: return this ;
726: }
727:
728: public void sleep(long millis) throws InterruptedException {
729: ensureCurrent();
730: synchronized (stopLock) {
731: try {
732: isStopped = true;
733: stopLock.wait(millis);
734: } finally {
735: isStopped = false;
736: pollThreadEvents();
737: }
738: }
739: }
740:
741: public IRubyObject status() {
742: if (threadImpl.isAlive()) {
743: if (isStopped) {
744: return getRuntime().newString("sleep");
745: } else if (killed) {
746: return getRuntime().newString("aborting");
747: }
748:
749: return getRuntime().newString("run");
750: } else if (exitingException != null) {
751: return getRuntime().getNil();
752: } else {
753: return getRuntime().newBoolean(false);
754: }
755: }
756:
757: public IRubyObject kill() {
758: // need to reexamine this
759: RubyThread currentThread = getRuntime().getCurrentContext()
760: .getThread();
761:
762: try {
763: if (DEBUG)
764: System.out.println("thread " + Thread.currentThread()
765: + " trying to kill");
766: while (!(currentThread.lock.tryLock() && this .lock
767: .tryLock())) {
768: if (currentThread.lock.isHeldByCurrentThread())
769: currentThread.lock.unlock();
770: }
771:
772: currentThread.pollThreadEvents();
773:
774: if (DEBUG)
775: System.out.println("thread " + Thread.currentThread()
776: + " succeeded with kill");
777: killed = true;
778:
779: threadImpl.interrupt(); // break out of wait states and blocking IO
780: } finally {
781: if (currentThread.lock.isHeldByCurrentThread())
782: currentThread.lock.unlock();
783: if (this .lock.isHeldByCurrentThread())
784: this .lock.unlock();
785: }
786:
787: try {
788: threadImpl.join();
789: } catch (InterruptedException ie) {
790: // we were interrupted, check thread events again
791: currentThread.pollThreadEvents();
792: } catch (ExecutionException ie) {
793: // we were interrupted, check thread events again
794: currentThread.pollThreadEvents();
795: }
796:
797: return this ;
798: }
799:
800: public IRubyObject exit() {
801: return kill();
802: }
803:
804: private boolean isCurrent() {
805: return threadImpl.isCurrent();
806: }
807:
808: public void exceptionRaised(RaiseException exception) {
809: assert isCurrent();
810:
811: Ruby runtime = exception.getException().getRuntime();
812: if (abortOnException(runtime)) {
813: // FIXME: printError explodes on some nullpointer
814: //getRuntime().getRuntime().printError(exception.getException());
815: // TODO: Doesn't SystemExit have its own method to make this less wordy..
816: RubyException re = RubyException.newException(getRuntime(),
817: getRuntime().getClass("SystemExit"), exception
818: .getMessage());
819: re.setInstanceVariable("status", getRuntime().newFixnum(1));
820: threadService.getMainThread().raise(
821: new IRubyObject[] { re }, Block.NULL_BLOCK);
822: } else {
823: exitingException = exception;
824: }
825: }
826:
827: private boolean abortOnException(Ruby runtime) {
828: return (runtime.isGlobalAbortOnExceptionEnabled() || abortOnException);
829: }
830:
831: public static RubyThread mainThread(IRubyObject receiver) {
832: return receiver.getRuntime().getThreadService().getMainThread();
833: }
834: }
|