/Languages/Ruby/Libraries/Builtins/ThreadOps.cs

http://github.com/IronLanguages/main · C# · 782 lines · 591 code · 105 blank · 86 comment · 89 complexity · c6657f0dfe4e0410ebaa08fbdf178055 MD5 · raw file

  1. /* ****************************************************************************
  2. *
  3. * Copyright (c) Microsoft Corporation.
  4. *
  5. * This source code is subject to terms and conditions of the Apache License, Version 2.0. A
  6. * copy of the license can be found in the License.html file at the root of this distribution. If
  7. * you cannot locate the Apache License, Version 2.0, please send an email to
  8. * ironruby@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
  9. * by the terms of the Apache License, Version 2.0.
  10. *
  11. * You must not remove this notice, or any other, from this software.
  12. *
  13. *
  14. * ***************************************************************************/
  15. #if FEATURE_THREAD
  16. using System;
  17. using System.Collections.Generic;
  18. using System.Runtime.CompilerServices;
  19. using System.Runtime.InteropServices;
  20. using System.Text;
  21. using System.Threading;
  22. using IronRuby.Runtime;
  23. using Microsoft.Scripting;
  24. using Microsoft.Scripting.Runtime;
  25. using Microsoft.Scripting.Utils;
  26. namespace IronRuby.Builtins {
  27. using Debug = System.Diagnostics.Debug;
  28. /// <summary>
  29. /// Ruby threads are represented by CLR thread objects (System.Threading.Thread).
  30. /// Ruby 1.8.N has green threads where the language does the thread scheduling. We map the green threads
  31. /// directly to CLR threads.
  32. ///
  33. /// Ruby supports asynchronously manipulating of an arbitrary thread with methods like Thread#raise, Thread#exit, etc.
  34. /// For such methods, we use Thread.Abort which is unsafe. Howevever, Ruby 1.9 may not support green threads,
  35. /// and this will not be an issue then.
  36. /// </summary>
  37. [RubyClass("Thread", Extends = typeof(Thread), Inherits = typeof(object), BuildConfig = "FEATURE_THREAD")]
  38. public static class ThreadOps {
  39. static bool _globalAbortOnException;
  40. /// <summary>
  41. /// The ThreadState enumeration is a flag, and multiple values could be set simultaneously. Also,
  42. /// there is other state that IronRuby tracks. RubyThreadStatus flattens out the different states
  43. /// into non-overlapping values.
  44. /// </summary>
  45. private enum RubyThreadStatus {
  46. /// <summary>
  47. /// Ruby does not expose such a state. However, since IronRuby uses CLR threads, this state can exist for
  48. /// threads that are not created directly from Ruby code
  49. /// </summary>
  50. Unstarted,
  51. Running,
  52. Sleeping,
  53. Completed,
  54. /// <summary>
  55. /// If Thread#kill has been called, and the thread is not sleeping
  56. /// </summary>
  57. Aborting,
  58. /// <summary>
  59. /// An unhandled exception was thrown by the thread
  60. /// </summary>
  61. Aborted
  62. }
  63. internal class RubyThreadInfo {
  64. private static readonly Dictionary<int, RubyThreadInfo> _mapping = new Dictionary<int, RubyThreadInfo>();
  65. private readonly Dictionary<RubySymbol, object> _threadLocalStorage;
  66. private ThreadGroup _group;
  67. private readonly Thread _thread;
  68. private bool _blocked;
  69. private bool _abortOnException;
  70. private AutoResetEvent _runSignal = new AutoResetEvent(false);
  71. private bool _isSleeping;
  72. private RubyThreadInfo(Thread thread) {
  73. _threadLocalStorage = new Dictionary<RubySymbol, object>();
  74. _group = ThreadGroup.Default;
  75. _thread = thread;
  76. }
  77. internal static RubyThreadInfo FromThread(Thread t) {
  78. RubyThreadInfo result;
  79. lock (_mapping) {
  80. int key = t.ManagedThreadId;
  81. if (!_mapping.TryGetValue(key, out result)) {
  82. result = new RubyThreadInfo(t);
  83. _mapping[key] = result;
  84. }
  85. }
  86. return result;
  87. }
  88. internal static void RegisterThread(Thread t) {
  89. FromThread(t);
  90. }
  91. internal object this[RubySymbol/*!*/ key] {
  92. get {
  93. lock (_threadLocalStorage) {
  94. object result;
  95. if (!_threadLocalStorage.TryGetValue(key, out result)) {
  96. result = null;
  97. }
  98. return result;
  99. }
  100. }
  101. set {
  102. lock (_threadLocalStorage) {
  103. if (value == null) {
  104. _threadLocalStorage.Remove(key);
  105. } else {
  106. _threadLocalStorage[key] = value;
  107. }
  108. }
  109. }
  110. }
  111. internal bool HasKey(RubySymbol/*!*/ key) {
  112. lock (_threadLocalStorage) {
  113. return _threadLocalStorage.ContainsKey(key);
  114. }
  115. }
  116. internal RubyArray GetKeys() {
  117. lock (_threadLocalStorage) {
  118. RubyArray result = new RubyArray(_threadLocalStorage.Count);
  119. foreach (RubySymbol key in _threadLocalStorage.Keys) {
  120. result.Add(key);
  121. }
  122. return result;
  123. }
  124. }
  125. internal ThreadGroup Group {
  126. get {
  127. return _group;
  128. }
  129. set {
  130. Interlocked.Exchange(ref _group, value);
  131. }
  132. }
  133. internal Thread Thread {
  134. get {
  135. return _thread;
  136. }
  137. }
  138. internal Exception Exception { get; set; }
  139. internal object Result { get; set; }
  140. internal bool CreatedFromRuby { get; set; }
  141. internal bool ExitRequested { get; set; }
  142. internal bool Blocked {
  143. get {
  144. return _blocked;
  145. }
  146. set {
  147. System.Diagnostics.Debug.Assert(Thread.CurrentThread == _thread);
  148. _blocked = value;
  149. }
  150. }
  151. internal bool AbortOnException {
  152. get {
  153. return _abortOnException;
  154. }
  155. set {
  156. _abortOnException = value;
  157. }
  158. }
  159. internal static RubyThreadInfo[] Threads {
  160. get {
  161. lock (_mapping) {
  162. List<RubyThreadInfo> result = new List<RubyThreadInfo>(_mapping.Count);
  163. foreach (KeyValuePair<int, RubyThreadInfo> entry in _mapping) {
  164. if (entry.Value.Thread.IsAlive) {
  165. result.Add(entry.Value);
  166. }
  167. }
  168. return result.ToArray();
  169. }
  170. }
  171. }
  172. /// <summary>
  173. /// We do not use Thread.Sleep here as another thread can call Thread#wakeup/Thread#run. Instead, we use our own
  174. /// lock which can be signalled from another thread.
  175. /// </summary>
  176. internal void Sleep() {
  177. try {
  178. _isSleeping = true;
  179. _runSignal.WaitOne();
  180. } finally {
  181. _isSleeping = false;
  182. }
  183. }
  184. internal void Run() {
  185. if (_isSleeping) {
  186. _runSignal.Set();
  187. }
  188. }
  189. }
  190. // declared private instance methods:
  191. // initialize
  192. // declared protected instance methods:
  193. // declared public instance methods:
  194. private static Exception MakeKeyTypeException(RubyContext/*!*/ context, object key) {
  195. if (key == null) {
  196. return RubyExceptions.CreateTypeError("nil is not a symbol");
  197. } else {
  198. // MRI calls RubyUtils.InspectObject, but this should be good enought as an error message:
  199. return RubyExceptions.CreateArgumentError("{0} is not a symbol", context.GetClassOf(key).Name);
  200. }
  201. }
  202. [RubyMethod("[]")]
  203. public static object GetElement(Thread/*!*/ self, [NotNull]RubySymbol/*!*/ key) {
  204. RubyThreadInfo info = RubyThreadInfo.FromThread(self);
  205. return info[key];
  206. }
  207. [RubyMethod("[]")]
  208. public static object GetElement(RubyContext/*!*/ context, Thread/*!*/ self, [NotNull]MutableString/*!*/ key) {
  209. return GetElement(self, context.CreateSymbol(key));
  210. }
  211. [RubyMethod("[]")]
  212. public static object GetElement(RubyContext/*!*/ context, Thread/*!*/ self, object key) {
  213. throw MakeKeyTypeException(context, key);
  214. }
  215. [RubyMethod("[]=")]
  216. public static object SetElement(Thread/*!*/ self, [NotNull]RubySymbol/*!*/ key, object value) {
  217. RubyThreadInfo info = RubyThreadInfo.FromThread(self);
  218. info[key] = value;
  219. return value;
  220. }
  221. [RubyMethod("[]=")]
  222. public static object SetElement(RubyContext/*!*/ context, Thread/*!*/ self, [NotNull]MutableString/*!*/ key, object value) {
  223. return SetElement(self, context.CreateSymbol(key), value);
  224. }
  225. [RubyMethod("[]=")]
  226. public static object SetElement(RubyContext/*!*/ context, Thread/*!*/ self, object key, object value) {
  227. throw MakeKeyTypeException(context, key);
  228. }
  229. [RubyMethod("abort_on_exception")]
  230. public static object AbortOnException(Thread/*!*/ self) {
  231. RubyThreadInfo info = RubyThreadInfo.FromThread(self);
  232. return info.AbortOnException;
  233. }
  234. [RubyMethod("abort_on_exception=")]
  235. public static object AbortOnException(Thread/*!*/ self, bool value) {
  236. RubyThreadInfo info = RubyThreadInfo.FromThread(self);
  237. info.AbortOnException = value;
  238. return value;
  239. }
  240. [RubyMethod("alive?")]
  241. public static bool IsAlive(Thread/*!*/ self) {
  242. RubyThreadInfo.RegisterThread(Thread.CurrentThread);
  243. return self.IsAlive;
  244. }
  245. [RubyMethod("group")]
  246. public static ThreadGroup Group(Thread/*!*/ self) {
  247. RubyThreadInfo info = RubyThreadInfo.FromThread(self);
  248. return info.Group;
  249. }
  250. [RubyMethod("inspect")]
  251. public static MutableString/*!*/ Inspect(RubyContext/*!*/ context, Thread/*!*/ self) {
  252. RubyThreadInfo.RegisterThread(Thread.CurrentThread);
  253. MutableString result = MutableString.CreateMutable(context.GetIdentifierEncoding());
  254. result.Append("#<");
  255. result.Append(context.GetClassDisplayName(self));
  256. result.Append(':');
  257. RubyUtils.AppendFormatHexObjectId(result, RubyUtils.GetObjectId(context, self));
  258. result.Append(' ');
  259. RubyThreadStatus status = GetStatus(self);
  260. switch (status) {
  261. case RubyThreadStatus.Unstarted:
  262. result.Append("unstarted");
  263. break;
  264. case RubyThreadStatus.Running:
  265. result.Append("run");
  266. break;
  267. case RubyThreadStatus.Sleeping:
  268. result.Append("sleep");
  269. break;
  270. case RubyThreadStatus.Aborting:
  271. result.Append("aborting");
  272. break;
  273. case RubyThreadStatus.Completed:
  274. case RubyThreadStatus.Aborted:
  275. result.Append("dead");
  276. break;
  277. }
  278. result.Append('>');
  279. return result;
  280. }
  281. [RubyMethod("join")]
  282. public static Thread/*!*/ Join(Thread/*!*/ self) {
  283. RubyThreadInfo.RegisterThread(Thread.CurrentThread);
  284. self.Join();
  285. Exception threadException = RubyThreadInfo.FromThread(self).Exception;
  286. if (threadException != null) {
  287. throw threadException;
  288. }
  289. return self;
  290. }
  291. [RubyMethod("join")]
  292. public static Thread/*!*/ Join(Thread/*!*/ self, double seconds) {
  293. RubyThreadInfo.RegisterThread(Thread.CurrentThread);
  294. if (!(self.ThreadState == ThreadState.AbortRequested || self.ThreadState == ThreadState.Aborted)) {
  295. double ms = seconds * 1000;
  296. int timeout = (ms < Int32.MinValue || ms > Int32.MaxValue) ? Timeout.Infinite : (int)ms;
  297. if (!self.Join(timeout)) {
  298. return null;
  299. }
  300. }
  301. Exception threadException = RubyThreadInfo.FromThread(self).Exception;
  302. if (threadException != null) {
  303. throw threadException;
  304. }
  305. return self;
  306. }
  307. [RubyMethod("kill")]
  308. [RubyMethod("exit")]
  309. [RubyMethod("terminate")]
  310. public static Thread Kill(Thread/*!*/ self) {
  311. RubyThreadInfo.RegisterThread(Thread.CurrentThread);
  312. RubyThreadInfo info = RubyThreadInfo.FromThread(self);
  313. if (GetStatus(self) == RubyThreadStatus.Sleeping && info.ExitRequested) {
  314. // Thread must be sleeping in an ensure clause. Wake up the thread and allow ensure clause to complete
  315. info.Run();
  316. return self;
  317. }
  318. info.ExitRequested = true;
  319. RubyUtils.ExitThread(self);
  320. return self;
  321. }
  322. [RubyMethod("key?")]
  323. public static object HasKey(Thread/*!*/ self, [NotNull]RubySymbol/*!*/ key) {
  324. RubyThreadInfo info = RubyThreadInfo.FromThread(self);
  325. return info.HasKey(key);
  326. }
  327. [RubyMethod("key?")]
  328. public static object HasKey(RubyContext/*!*/ context, Thread/*!*/ self, [NotNull]MutableString/*!*/ key) {
  329. return HasKey(self, context.CreateSymbol(key));
  330. }
  331. [RubyMethod("key?")]
  332. public static object HasKey(RubyContext/*!*/ context, Thread/*!*/ self, object key) {
  333. throw MakeKeyTypeException(context, key);
  334. }
  335. [RubyMethod("keys")]
  336. public static object Keys(RubyContext/*!*/ context, Thread/*!*/ self) {
  337. RubyThreadInfo info = RubyThreadInfo.FromThread(self);
  338. return info.GetKeys();
  339. }
  340. #if !SILVERLIGHT && !WP75
  341. #region priority, priority=
  342. [RubyMethod("priority", BuildConfig = "!SILVERLIGHT")]
  343. public static object Priority(Thread/*!*/ self) {
  344. RubyThreadInfo.RegisterThread(Thread.CurrentThread);
  345. switch (self.Priority) {
  346. case ThreadPriority.Lowest:
  347. return -2;
  348. case ThreadPriority.BelowNormal:
  349. return -1;
  350. case ThreadPriority.Normal:
  351. return 0;
  352. case ThreadPriority.AboveNormal:
  353. return 1;
  354. case ThreadPriority.Highest:
  355. return 2;
  356. default:
  357. return 0;
  358. }
  359. }
  360. [RubyMethod("priority=", BuildConfig = "!SILVERLIGHT")]
  361. public static Thread Priority(Thread/*!*/ self, int priority) {
  362. RubyThreadInfo.RegisterThread(Thread.CurrentThread);
  363. if (priority <= -2)
  364. self.Priority = ThreadPriority.Lowest;
  365. else if (priority == -1)
  366. self.Priority = ThreadPriority.BelowNormal;
  367. else if (priority == 0)
  368. self.Priority = ThreadPriority.Normal;
  369. else if (priority == 1)
  370. self.Priority = ThreadPriority.AboveNormal;
  371. else
  372. self.Priority = ThreadPriority.Highest;
  373. return self;
  374. }
  375. #endregion
  376. #endif
  377. #region raise, fail
  378. #if FEATURE_EXCEPTION_STATE
  379. private static void RaiseAsyncException(Thread thread, Exception exception) {
  380. RubyThreadStatus status = GetStatus(thread);
  381. // rethrow semantics, preserves the backtrace associated with the exception:
  382. RubyUtils.RaiseAsyncException(thread, exception);
  383. if (status == RubyThreadStatus.Sleeping) {
  384. // Thread.Abort can interrupt a thread with ThreadState.WaitSleepJoin. However, Thread.Abort
  385. // is deferred while the thread is in a catch block. If there is a Kernel.sleep in a catch block,
  386. // then that sleep will not be interrupted.
  387. // TODO: We should call Run to nudge the thread if its CurrentException is not-null, and
  388. // ThreadOps.Stop should have a checkpoint to see whether an async exception needs to be thrown
  389. // Run(thread);
  390. }
  391. }
  392. #endif
  393. [RubyMethod("raise")]
  394. [RubyStackTraceHidden]
  395. public static void RaiseException(RubyContext/*!*/ context, Thread/*!*/ self) {
  396. if (self == Thread.CurrentThread) {
  397. KernelOps.RaiseException(context, self);
  398. return;
  399. }
  400. #if FEATURE_EXCEPTION_STATE
  401. // TODO: RubyContext.CurrentException is a thread-local static, and cannot be accessed from other threads
  402. // To fix this, it would have to be stored somehow without using ThreadStaticAttribute
  403. // For now, we just throw a RuntimeError
  404. RaiseAsyncException(self, new RuntimeError());
  405. #else
  406. throw new NotImplementedError("Thread#raise not supported on this platform");
  407. #endif
  408. }
  409. [RubyMethod("raise")]
  410. [RubyStackTraceHidden]
  411. public static void RaiseException(Thread/*!*/ self, [NotNull]MutableString/*!*/ message) {
  412. if (self == Thread.CurrentThread) {
  413. KernelOps.RaiseException(self, message);
  414. return;
  415. }
  416. #if FEATURE_EXCEPTION_STATE
  417. Exception e = RubyExceptionData.InitializeException(new RuntimeError(message.ToString()), message);
  418. RaiseAsyncException(self, e);
  419. #else
  420. throw new NotImplementedError("Thread#raise not supported on this platform");
  421. #endif
  422. }
  423. [RubyMethod("raise")]
  424. [RubyStackTraceHidden]
  425. public static void RaiseException(RespondToStorage/*!*/ respondToStorage, UnaryOpStorage/*!*/ storage0, BinaryOpStorage/*!*/ storage1,
  426. CallSiteStorage<Action<CallSite, Exception, RubyArray>>/*!*/ setBackTraceStorage,
  427. Thread/*!*/ self, object/*!*/ obj, [Optional]object arg, [Optional]RubyArray backtrace) {
  428. if (self == Thread.CurrentThread) {
  429. KernelOps.RaiseException(respondToStorage, storage0, storage1, setBackTraceStorage, self, obj, arg, backtrace);
  430. return;
  431. }
  432. #if FEATURE_EXCEPTION_STATE
  433. Exception e = KernelOps.CreateExceptionToRaise(respondToStorage, storage0, storage1, setBackTraceStorage, obj, arg, backtrace);
  434. RaiseAsyncException(self, e);
  435. #else
  436. throw new NotImplementedError("Thread#raise not supported on this platform");
  437. #endif
  438. }
  439. #endregion
  440. // safe_level
  441. // TODO: these two methods interrupt a sleeping thread via the Thread.Interrupt API.
  442. // Unfortunately, this API interrupts the sleeping thread by throwing a ThreadInterruptedException.
  443. // In many Ruby programs (eg the specs) this causes the thread to terminate, which is NOT the
  444. // expected behavior. This is tracked by Rubyforge bug # 21157
  445. #if !SILVERLIGHT
  446. [RubyMethod("run", BuildConfig = "!SILVERLIGHT")]
  447. [RubyMethod("wakeup", BuildConfig = "!SILVERLIGHT")]
  448. public static Thread Run(Thread/*!*/ self) {
  449. RubyThreadInfo.RegisterThread(Thread.CurrentThread);
  450. RubyThreadInfo info = RubyThreadInfo.FromThread(self);
  451. info.Run();
  452. return self;
  453. }
  454. #endif
  455. private static RubyThreadStatus GetStatus(Thread thread) {
  456. ThreadState state = thread.ThreadState;
  457. RubyThreadInfo info = RubyThreadInfo.FromThread(thread);
  458. if ((state & ThreadState.Unstarted) == ThreadState.Unstarted) {
  459. if (info.CreatedFromRuby) {
  460. // Ruby threads do not have an unstarted status. We must be in the tiny window when ThreadOps.CreateThread
  461. // created the thread, but has not called Thread.Start on it yet.
  462. return RubyThreadStatus.Running;
  463. } else {
  464. // This is a thread created from outside Ruby. In such a case, we do not know when Thread.Start
  465. // will be called on it. So we report it as unstarted.
  466. return RubyThreadStatus.Unstarted;
  467. }
  468. }
  469. if ((state & (ThreadState.Stopped|ThreadState.Aborted)) != 0) {
  470. if (RubyThreadInfo.FromThread(thread).Exception == null) {
  471. return RubyThreadStatus.Completed;
  472. } else {
  473. return RubyThreadStatus.Aborted;
  474. }
  475. }
  476. if ((state & ThreadState.WaitSleepJoin) == ThreadState.WaitSleepJoin) {
  477. // We will report a thread to be sleeping more often than in CRuby. This is because any "lock" statement
  478. // can potentially cause ThreadState.WaitSleepJoin. Also, "Thread.pass" does System.Threading.Thread.Sleep(0)
  479. // which also briefly changes the state to ThreadState.WaitSleepJoin
  480. return RubyThreadStatus.Sleeping;
  481. }
  482. if ((state & ThreadState.AbortRequested) != 0) {
  483. return RubyThreadStatus.Aborting;
  484. }
  485. if ((state & ThreadState.Running) == ThreadState.Running) {
  486. if (info.Blocked) {
  487. return RubyThreadStatus.Sleeping;
  488. } else {
  489. return RubyThreadStatus.Running;
  490. }
  491. }
  492. #pragma warning disable 162 // msc: unreachable code
  493. throw new ArgumentException("unknown thread status: " + state);
  494. #pragma warning restore 162
  495. }
  496. [RubyMethod("status")]
  497. public static object Status(Thread/*!*/ self) {
  498. RubyThreadInfo.RegisterThread(Thread.CurrentThread);
  499. switch (GetStatus(self)) {
  500. case RubyThreadStatus.Unstarted:
  501. return MutableString.CreateAscii("unstarted");
  502. case RubyThreadStatus.Running:
  503. return MutableString.CreateAscii("run");
  504. case RubyThreadStatus.Sleeping:
  505. return MutableString.CreateAscii("sleep");
  506. case RubyThreadStatus.Aborting:
  507. return MutableString.CreateAscii("aborting");
  508. case RubyThreadStatus.Completed:
  509. return false;
  510. case RubyThreadStatus.Aborted:
  511. return null;
  512. default:
  513. throw new ArgumentException("unknown thread status");
  514. }
  515. }
  516. [RubyMethod("value")]
  517. public static object Value(Thread/*!*/ self) {
  518. Join(self);
  519. return RubyThreadInfo.FromThread(self).Result;
  520. }
  521. // stop?
  522. // declared singleton methods
  523. [RubyMethod("abort_on_exception", RubyMethodAttributes.PublicSingleton)]
  524. public static object GlobalAbortOnException(object self) {
  525. return _globalAbortOnException;
  526. }
  527. [RubyMethod("abort_on_exception=", RubyMethodAttributes.PublicSingleton)]
  528. public static object GlobalAbortOnException(object self, bool value) {
  529. _globalAbortOnException = value;
  530. return value;
  531. }
  532. private static void SetCritical(RubyContext/*!*/ context, bool value) {
  533. // Debug.Assert(context.RubyOptions.Compatibility < RubyCompatibility.Ruby19);
  534. if (value) {
  535. bool lockTaken = false;
  536. try {
  537. MonitorUtils.Enter(context.CriticalMonitor, ref lockTaken);
  538. } finally {
  539. // thread could have been aborted just before/after Monitor.Enter acquired the lock:
  540. if (lockTaken) {
  541. context.CriticalThread = Thread.CurrentThread;
  542. }
  543. }
  544. } else {
  545. Monitor.Exit(context.CriticalMonitor);
  546. context.CriticalThread = null;
  547. }
  548. }
  549. [RubyMethod("critical", RubyMethodAttributes.PublicSingleton)] // Compatibility <= RubyCompatibility.Ruby18
  550. public static bool Critical(RubyContext/*!*/ context, object self) {
  551. RubyThreadInfo.RegisterThread(Thread.CurrentThread);
  552. return context.CriticalThread != null;
  553. }
  554. [RubyMethod("critical=", RubyMethodAttributes.PublicSingleton)]
  555. public static void Critical(RubyContext/*!*/ context, object self, bool value) {
  556. RubyThreadInfo.RegisterThread(Thread.CurrentThread);
  557. SetCritical(context, value);
  558. }
  559. [RubyMethod("current", RubyMethodAttributes.PublicSingleton)]
  560. public static Thread/*!*/ Current(object self) {
  561. RubyThreadInfo.RegisterThread(Thread.CurrentThread);
  562. return Thread.CurrentThread;
  563. }
  564. // exclusive
  565. // fork
  566. [RubyMethod("list", RubyMethodAttributes.PublicSingleton)]
  567. public static RubyArray/*!*/ List(object self) {
  568. RubyThreadInfo.RegisterThread(Thread.CurrentThread);
  569. RubyThreadInfo[] threads = RubyThreadInfo.Threads;
  570. RubyArray result = new RubyArray(threads.Length);
  571. foreach (RubyThreadInfo threadInfo in threads) {
  572. Thread thread = threadInfo.Thread;
  573. if (thread != null) {
  574. result.Add(thread);
  575. }
  576. }
  577. return result;
  578. }
  579. [RubyMethod("main", RubyMethodAttributes.PublicSingleton)]
  580. public static Thread/*!*/ GetMainThread(RubyContext/*!*/ context, RubyClass self) {
  581. return context.MainThread;
  582. }
  583. [RubyMethod("new", RubyMethodAttributes.PublicSingleton)]
  584. [RubyMethod("start", RubyMethodAttributes.PublicSingleton)]
  585. public static Thread/*!*/ CreateThread(RubyContext/*!*/ context, BlockParam startRoutine, object self, params object[]/*!*/ args) {
  586. if (startRoutine == null) {
  587. throw new ThreadError("must be called with a block");
  588. }
  589. ThreadGroup group = Group(Thread.CurrentThread);
  590. Thread result = new Thread(new ThreadStart(() => RubyThreadStart(context, startRoutine, args, group)));
  591. // Ruby exits when the main thread exits. So all other threads need to be marked as background threads
  592. result.IsBackground = true;
  593. result.Start();
  594. return result;
  595. }
  596. private static void RubyThreadStart(RubyContext/*!*/ context, BlockParam/*!*/ startRoutine, object[]/*!*/ args, ThreadGroup group) {
  597. RubyThreadInfo info = RubyThreadInfo.FromThread(Thread.CurrentThread);
  598. info.CreatedFromRuby = true;
  599. info.Group = group;
  600. try {
  601. object threadResult;
  602. // TODO: break/returns might throw LocalJumpError if the RFC that was created for startRoutine is not active anymore:
  603. if (startRoutine.Yield(args, out threadResult) && startRoutine.Returning(threadResult, out threadResult)) {
  604. info.Exception = new ThreadError("return can't jump across threads");
  605. }
  606. info.Result = threadResult;
  607. } catch (MethodUnwinder) {
  608. info.Exception = new ThreadError("return can't jump across threads");
  609. } catch (Exception e) {
  610. if (info.ExitRequested) {
  611. // Note that "e" may not be ThreadAbortException at this point If an exception was raised from a finally block,
  612. // we will get that here instead
  613. Utils.Log(String.Format("Thread {0} exited.", info.Thread.ManagedThreadId), "THREAD");
  614. info.Result = false;
  615. #if FEATURE_EXCEPTION_STATE
  616. Thread.ResetAbort();
  617. #endif
  618. } else {
  619. e = RubyUtils.GetVisibleException(e);
  620. RubyExceptionData.ActiveExceptionHandled(e);
  621. info.Exception = e;
  622. StringBuilder trace = new StringBuilder();
  623. trace.Append(e.Message);
  624. trace.AppendLine();
  625. trace.AppendLine();
  626. trace.Append(e.StackTrace);
  627. trace.AppendLine();
  628. trace.AppendLine();
  629. RubyExceptionData data = RubyExceptionData.GetInstance(e);
  630. if (data.Backtrace != null) {
  631. foreach (var frame in data.Backtrace) {
  632. trace.Append(frame.ToString());
  633. }
  634. }
  635. Utils.Log(trace.ToString(), "THREAD");
  636. if (_globalAbortOnException || info.AbortOnException) {
  637. throw;
  638. }
  639. }
  640. } finally {
  641. // Its not a good idea to terminate a thread which has set Thread.critical=true, but its hard to predict
  642. // which thread will be scheduled next, even with green threads. However, ConditionVariable.create_timer
  643. // in monitor.rb explicitly does "Thread.critical=true; other_thread.raise" before exiting, and expects
  644. // other_thread to be scheduled immediately.
  645. // To deal with such code, we release the critical monitor here if the current thread is holding it
  646. if (context.RubyOptions.Compatibility < RubyCompatibility.Ruby19 && context.CriticalThread == Thread.CurrentThread) {
  647. SetCritical(context, false);
  648. }
  649. }
  650. }
  651. [RubyMethod("pass", RubyMethodAttributes.PublicSingleton)]
  652. public static void Yield(object self) {
  653. RubyThreadInfo.RegisterThread(Thread.CurrentThread);
  654. Thread.Sleep(0);
  655. }
  656. [RubyMethod("stop", RubyMethodAttributes.PublicSingleton)]
  657. public static void Stop(RubyContext/*!*/ context, object self) {
  658. if (context.CriticalThread == Thread.CurrentThread) {
  659. SetCritical(context, false);
  660. }
  661. DoSleep();
  662. }
  663. internal static void DoSleep() {
  664. RubyThreadInfo.RegisterThread(Thread.CurrentThread);
  665. // TODO: MRI throws an exception if you try to stop the main thread
  666. RubyThreadInfo info = RubyThreadInfo.FromThread(Thread.CurrentThread);
  667. info.Sleep();
  668. }
  669. [RubyMethod("stop?", RubyMethodAttributes.PublicInstance)]
  670. public static bool IsStopped(Thread self) {
  671. RubyThreadInfo.RegisterThread(Thread.CurrentThread);
  672. RubyThreadStatus status = GetStatus(self);
  673. return status == RubyThreadStatus.Sleeping || status == RubyThreadStatus.Completed || status == RubyThreadStatus.Aborted;
  674. }
  675. }
  676. }
  677. #endif