ThreadReferenceImpl.java revision 11099:678faa7d1a6a
1/* 2 * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package com.sun.tools.jdi; 27 28import com.sun.jdi.*; 29import com.sun.jdi.request.BreakpointRequest; 30import java.util.*; 31import java.lang.ref.WeakReference; 32 33public class ThreadReferenceImpl extends ObjectReferenceImpl 34 implements ThreadReference, VMListener { 35 static final int SUSPEND_STATUS_SUSPENDED = 0x1; 36 static final int SUSPEND_STATUS_BREAK = 0x2; 37 38 private int suspendedZombieCount = 0; 39 40 /* 41 * Some objects can only be created while a thread is suspended and are valid 42 * only while the thread remains suspended. Examples are StackFrameImpl 43 * and MonitorInfoImpl. When the thread resumes, these objects have to be 44 * marked as invalid so that their methods can throw 45 * InvalidStackFrameException if they are called. To do this, such objects 46 * register themselves as listeners of the associated thread. When the 47 * thread is resumed, its listeners are notified and mark themselves 48 * invalid. 49 * Also, note that ThreadReferenceImpl itself caches some info that 50 * is valid only as long as the thread is suspended. When the thread 51 * is resumed, that cache must be purged. 52 * Lastly, note that ThreadReferenceImpl and its super, ObjectReferenceImpl 53 * cache some info that is only valid as long as the entire VM is suspended. 54 * If _any_ thread is resumed, this cache must be purged. To handle this, 55 * both ThreadReferenceImpl and ObjectReferenceImpl register themselves as 56 * VMListeners so that they get notified when all threads are suspended and 57 * when any thread is resumed. 58 */ 59 60 // This is cached for the life of the thread 61 private ThreadGroupReference threadGroup; 62 63 // This is cached only while this one thread is suspended. Each time 64 // the thread is resumed, we abandon the current cache object and 65 // create a new initialized one. 66 private static class LocalCache { 67 JDWP.ThreadReference.Status status = null; 68 List<StackFrame> frames = null; 69 int framesStart = -1; 70 int framesLength = 0; 71 int frameCount = -1; 72 List<ObjectReference> ownedMonitors = null; 73 List<MonitorInfo> ownedMonitorsInfo = null; 74 ObjectReference contendedMonitor = null; 75 boolean triedCurrentContended = false; 76 } 77 78 /* 79 * The localCache instance var is set by resetLocalCache to an initialized 80 * object as shown above. This occurs when the ThreadReference 81 * object is created, and when the mirrored thread is resumed. 82 * The fields are then filled in by the relevant methods as they 83 * are called. A problem can occur if resetLocalCache is called 84 * (ie, a resume() is executed) at certain points in the execution 85 * of some of these methods - see 6751643. To avoid this, each 86 * method that wants to use this cache must make a local copy of 87 * this variable and use that. This means that each invocation of 88 * these methods will use a copy of the cache object that was in 89 * effect at the point that the copy was made; if a racy resume 90 * occurs, it won't affect the method's local copy. This means that 91 * the values returned by these calls may not match the state of 92 * the debuggee at the time the caller gets the values. EG, 93 * frameCount() is called and comes up with 5 frames. But before 94 * it returns this, a resume of the debuggee thread is executed in a 95 * different debugger thread. The thread is resumed and running at 96 * the time that the value 5 is returned. Or even worse, the thread 97 * could be suspended again and have a different number of frames, eg, 24, 98 * but this call will still return 5. 99 */ 100 private LocalCache localCache; 101 102 private void resetLocalCache() { 103 localCache = new LocalCache(); 104 } 105 106 // This is cached only while all threads in the VM are suspended 107 // Yes, someone could change the name of a thread while it is suspended. 108 private static class Cache extends ObjectReferenceImpl.Cache { 109 String name = null; 110 } 111 protected ObjectReferenceImpl.Cache newCache() { 112 return new Cache(); 113 } 114 115 // Listeners - synchronized on vm.state() 116 private List<WeakReference<ThreadListener>> listeners = new ArrayList<WeakReference<ThreadListener>>(); 117 118 119 ThreadReferenceImpl(VirtualMachine aVm, long aRef) { 120 super(aVm,aRef); 121 resetLocalCache(); 122 vm.state().addListener(this); 123 } 124 125 protected String description() { 126 return "ThreadReference " + uniqueID(); 127 } 128 129 /* 130 * VMListener implementation 131 */ 132 public boolean vmNotSuspended(VMAction action) { 133 if (action.resumingThread() == null) { 134 // all threads are being resumed 135 synchronized (vm.state()) { 136 processThreadAction(new ThreadAction(this, 137 ThreadAction.THREAD_RESUMABLE)); 138 } 139 140 } 141 142 /* 143 * Othewise, only one thread is being resumed: 144 * if it is us, 145 * we have already done our processThreadAction to notify our 146 * listeners when we processed the resume. 147 * if it is not us, 148 * we don't want to notify our listeners 149 * because we are not being resumed. 150 */ 151 return super.vmNotSuspended(action); 152 } 153 154 /** 155 * Note that we only cache the name string while the entire VM is suspended 156 * because the name can change via Thread.setName arbitrarily while this 157 * thread is running. 158 */ 159 public String name() { 160 String name = null; 161 try { 162 Cache local = (Cache)getCache(); 163 164 if (local != null) { 165 name = local.name; 166 } 167 if (name == null) { 168 name = JDWP.ThreadReference.Name.process(vm, this) 169 .threadName; 170 if (local != null) { 171 local.name = name; 172 } 173 } 174 } catch (JDWPException exc) { 175 throw exc.toJDIException(); 176 } 177 return name; 178 } 179 180 /* 181 * Sends a command to the back end which is defined to do an 182 * implicit vm-wide resume. 183 */ 184 PacketStream sendResumingCommand(CommandSender sender) { 185 synchronized (vm.state()) { 186 processThreadAction(new ThreadAction(this, 187 ThreadAction.THREAD_RESUMABLE)); 188 return sender.send(); 189 } 190 } 191 192 public void suspend() { 193 try { 194 JDWP.ThreadReference.Suspend.process(vm, this); 195 } catch (JDWPException exc) { 196 throw exc.toJDIException(); 197 } 198 // Don't consider the thread suspended yet. On reply, notifySuspend() 199 // will be called. 200 } 201 202 public void resume() { 203 /* 204 * If it's a zombie, we can just update internal state without 205 * going to back end. 206 */ 207 if (suspendedZombieCount > 0) { 208 suspendedZombieCount--; 209 return; 210 } 211 212 PacketStream stream; 213 synchronized (vm.state()) { 214 processThreadAction(new ThreadAction(this, 215 ThreadAction.THREAD_RESUMABLE)); 216 stream = JDWP.ThreadReference.Resume.enqueueCommand(vm, this); 217 } 218 try { 219 JDWP.ThreadReference.Resume.waitForReply(vm, stream); 220 } catch (JDWPException exc) { 221 throw exc.toJDIException(); 222 } 223 } 224 225 public int suspendCount() { 226 /* 227 * If it's a zombie, we maintain the count in the front end. 228 */ 229 if (suspendedZombieCount > 0) { 230 return suspendedZombieCount; 231 } 232 233 try { 234 return JDWP.ThreadReference.SuspendCount.process(vm, this).suspendCount; 235 } catch (JDWPException exc) { 236 throw exc.toJDIException(); 237 } 238 } 239 240 public void stop(ObjectReference throwable) throws InvalidTypeException { 241 validateMirror(throwable); 242 // Verify that the given object is a Throwable instance 243 List<ReferenceType> list = vm.classesByName("java.lang.Throwable"); 244 ClassTypeImpl throwableClass = (ClassTypeImpl)list.get(0); 245 if ((throwable == null) || 246 !throwableClass.isAssignableFrom(throwable)) { 247 throw new InvalidTypeException("Not an instance of Throwable"); 248 } 249 250 try { 251 JDWP.ThreadReference.Stop.process(vm, this, 252 (ObjectReferenceImpl)throwable); 253 } catch (JDWPException exc) { 254 throw exc.toJDIException(); 255 } 256 } 257 258 public void interrupt() { 259 try { 260 JDWP.ThreadReference.Interrupt.process(vm, this); 261 } catch (JDWPException exc) { 262 throw exc.toJDIException(); 263 } 264 } 265 266 private JDWP.ThreadReference.Status jdwpStatus() { 267 LocalCache snapshot = localCache; 268 JDWP.ThreadReference.Status myStatus = snapshot.status; 269 try { 270 if (myStatus == null) { 271 myStatus = JDWP.ThreadReference.Status.process(vm, this); 272 if ((myStatus.suspendStatus & SUSPEND_STATUS_SUSPENDED) != 0) { 273 // thread is suspended, we can cache the status. 274 snapshot.status = myStatus; 275 } 276 } 277 } catch (JDWPException exc) { 278 throw exc.toJDIException(); 279 } 280 return myStatus; 281 } 282 283 public int status() { 284 return jdwpStatus().threadStatus; 285 } 286 287 public boolean isSuspended() { 288 return ((suspendedZombieCount > 0) || 289 ((jdwpStatus().suspendStatus & SUSPEND_STATUS_SUSPENDED) != 0)); 290 } 291 292 public boolean isAtBreakpoint() { 293 /* 294 * TO DO: This fails to take filters into account. 295 */ 296 try { 297 StackFrame frame = frame(0); 298 Location location = frame.location(); 299 List<BreakpointRequest> requests = vm.eventRequestManager().breakpointRequests(); 300 Iterator<BreakpointRequest> iter = requests.iterator(); 301 while (iter.hasNext()) { 302 BreakpointRequest request = iter.next(); 303 if (location.equals(request.location())) { 304 return true; 305 } 306 } 307 return false; 308 } catch (IndexOutOfBoundsException iobe) { 309 return false; // no frames on stack => not at breakpoint 310 } catch (IncompatibleThreadStateException itse) { 311 // Per the javadoc, not suspended => return false 312 return false; 313 } 314 } 315 316 public ThreadGroupReference threadGroup() { 317 /* 318 * Thread group can't change, so it's cached once and for all. 319 */ 320 if (threadGroup == null) { 321 try { 322 threadGroup = JDWP.ThreadReference.ThreadGroup. 323 process(vm, this).group; 324 } catch (JDWPException exc) { 325 throw exc.toJDIException(); 326 } 327 } 328 return threadGroup; 329 } 330 331 public int frameCount() throws IncompatibleThreadStateException { 332 LocalCache snapshot = localCache; 333 try { 334 if (snapshot.frameCount == -1) { 335 snapshot.frameCount = JDWP.ThreadReference.FrameCount 336 .process(vm, this).frameCount; 337 } 338 } catch (JDWPException exc) { 339 switch (exc.errorCode()) { 340 case JDWP.Error.THREAD_NOT_SUSPENDED: 341 case JDWP.Error.INVALID_THREAD: /* zombie */ 342 throw new IncompatibleThreadStateException(); 343 default: 344 throw exc.toJDIException(); 345 } 346 } 347 return snapshot.frameCount; 348 } 349 350 public List<StackFrame> frames() throws IncompatibleThreadStateException { 351 return privateFrames(0, -1); 352 } 353 354 public StackFrame frame(int index) throws IncompatibleThreadStateException { 355 List<StackFrame> list = privateFrames(index, 1); 356 return list.get(0); 357 } 358 359 /** 360 * Is the requested subrange within what has been retrieved? 361 * local is known to be non-null. Should only be called from 362 * a sync method. 363 */ 364 private boolean isSubrange(LocalCache snapshot, 365 int start, int length) { 366 if (start < snapshot.framesStart) { 367 return false; 368 } 369 if (length == -1) { 370 return (snapshot.framesLength == -1); 371 } 372 if (snapshot.framesLength == -1) { 373 if ((start + length) > (snapshot.framesStart + 374 snapshot.frames.size())) { 375 throw new IndexOutOfBoundsException(); 376 } 377 return true; 378 } 379 return ((start + length) <= (snapshot.framesStart + snapshot.framesLength)); 380 } 381 382 public List<StackFrame> frames(int start, int length) 383 throws IncompatibleThreadStateException { 384 if (length < 0) { 385 throw new IndexOutOfBoundsException( 386 "length must be greater than or equal to zero"); 387 } 388 return privateFrames(start, length); 389 } 390 391 /** 392 * Private version of frames() allows "-1" to specify all 393 * remaining frames. 394 */ 395 synchronized private List<StackFrame> privateFrames(int start, int length) 396 throws IncompatibleThreadStateException { 397 398 // Lock must be held while creating stack frames so if that two threads 399 // do this at the same time, one won't clobber the subset created by the other. 400 LocalCache snapshot = localCache; 401 try { 402 if (snapshot.frames == null || !isSubrange(snapshot, start, length)) { 403 JDWP.ThreadReference.Frames.Frame[] jdwpFrames 404 = JDWP.ThreadReference.Frames. 405 process(vm, this, start, length).frames; 406 int count = jdwpFrames.length; 407 snapshot.frames = new ArrayList<StackFrame>(count); 408 409 for (int i = 0; i<count; i++) { 410 if (jdwpFrames[i].location == null) { 411 throw new InternalException("Invalid frame location"); 412 } 413 StackFrame frame = new StackFrameImpl(vm, this, 414 jdwpFrames[i].frameID, 415 jdwpFrames[i].location); 416 // Add to the frame list 417 snapshot.frames.add(frame); 418 } 419 snapshot.framesStart = start; 420 snapshot.framesLength = length; 421 return Collections.unmodifiableList(snapshot.frames); 422 } else { 423 int fromIndex = start - snapshot.framesStart; 424 int toIndex; 425 if (length == -1) { 426 toIndex = snapshot.frames.size() - fromIndex; 427 } else { 428 toIndex = fromIndex + length; 429 } 430 return Collections.unmodifiableList(snapshot.frames.subList(fromIndex, toIndex)); 431 } 432 } catch (JDWPException exc) { 433 switch (exc.errorCode()) { 434 case JDWP.Error.THREAD_NOT_SUSPENDED: 435 case JDWP.Error.INVALID_THREAD: /* zombie */ 436 throw new IncompatibleThreadStateException(); 437 default: 438 throw exc.toJDIException(); 439 } 440 } 441 } 442 443 public List<ObjectReference> ownedMonitors() throws IncompatibleThreadStateException { 444 LocalCache snapshot = localCache; 445 try { 446 if (snapshot.ownedMonitors == null) { 447 snapshot.ownedMonitors = Arrays.asList( 448 (ObjectReference[])JDWP.ThreadReference.OwnedMonitors. 449 process(vm, this).owned); 450 if ((vm.traceFlags & VirtualMachine.TRACE_OBJREFS) != 0) { 451 vm.printTrace(description() + 452 " temporarily caching owned monitors"+ 453 " (count = " + snapshot.ownedMonitors.size() + ")"); 454 } 455 } 456 } catch (JDWPException exc) { 457 switch (exc.errorCode()) { 458 case JDWP.Error.THREAD_NOT_SUSPENDED: 459 case JDWP.Error.INVALID_THREAD: /* zombie */ 460 throw new IncompatibleThreadStateException(); 461 default: 462 throw exc.toJDIException(); 463 } 464 } 465 return snapshot.ownedMonitors; 466 } 467 468 public ObjectReference currentContendedMonitor() 469 throws IncompatibleThreadStateException { 470 LocalCache snapshot = localCache; 471 try { 472 if (snapshot.contendedMonitor == null && 473 !snapshot.triedCurrentContended) { 474 snapshot.contendedMonitor = JDWP.ThreadReference.CurrentContendedMonitor. 475 process(vm, this).monitor; 476 snapshot.triedCurrentContended = true; 477 if ((snapshot.contendedMonitor != null) && 478 ((vm.traceFlags & VirtualMachine.TRACE_OBJREFS) != 0)) { 479 vm.printTrace(description() + 480 " temporarily caching contended monitor"+ 481 " (id = " + snapshot.contendedMonitor.uniqueID() + ")"); 482 } 483 } 484 } catch (JDWPException exc) { 485 switch (exc.errorCode()) { 486 case JDWP.Error.THREAD_NOT_SUSPENDED: 487 case JDWP.Error.INVALID_THREAD: /* zombie */ 488 throw new IncompatibleThreadStateException(); 489 default: 490 throw exc.toJDIException(); 491 } 492 } 493 return snapshot.contendedMonitor; 494 } 495 496 public List<MonitorInfo> ownedMonitorsAndFrames() throws IncompatibleThreadStateException { 497 LocalCache snapshot = localCache; 498 try { 499 if (snapshot.ownedMonitorsInfo == null) { 500 JDWP.ThreadReference.OwnedMonitorsStackDepthInfo.monitor[] minfo; 501 minfo = JDWP.ThreadReference.OwnedMonitorsStackDepthInfo.process(vm, this).owned; 502 503 snapshot.ownedMonitorsInfo = new ArrayList<MonitorInfo>(minfo.length); 504 505 for (int i=0; i < minfo.length; i++) { 506 JDWP.ThreadReference.OwnedMonitorsStackDepthInfo.monitor mi = 507 minfo[i]; 508 MonitorInfo mon = new MonitorInfoImpl(vm, minfo[i].monitor, this, minfo[i].stack_depth); 509 snapshot.ownedMonitorsInfo.add(mon); 510 } 511 512 if ((vm.traceFlags & VirtualMachine.TRACE_OBJREFS) != 0) { 513 vm.printTrace(description() + 514 " temporarily caching owned monitors"+ 515 " (count = " + snapshot.ownedMonitorsInfo.size() + ")"); 516 } 517 } 518 519 } catch (JDWPException exc) { 520 switch (exc.errorCode()) { 521 case JDWP.Error.THREAD_NOT_SUSPENDED: 522 case JDWP.Error.INVALID_THREAD: /* zombie */ 523 throw new IncompatibleThreadStateException(); 524 default: 525 throw exc.toJDIException(); 526 } 527 } 528 return snapshot.ownedMonitorsInfo; 529 } 530 531 public void popFrames(StackFrame frame) throws IncompatibleThreadStateException { 532 // Note that interface-wise this functionality belongs 533 // here in ThreadReference, but implementation-wise it 534 // belongs in StackFrame, so we just forward it. 535 if (!frame.thread().equals(this)) { 536 throw new IllegalArgumentException("frame does not belong to this thread"); 537 } 538 if (!vm.canPopFrames()) { 539 throw new UnsupportedOperationException( 540 "target does not support popping frames"); 541 } 542 ((StackFrameImpl)frame).pop(); 543 } 544 545 public void forceEarlyReturn(Value returnValue) throws InvalidTypeException, 546 ClassNotLoadedException, 547 IncompatibleThreadStateException { 548 if (!vm.canForceEarlyReturn()) { 549 throw new UnsupportedOperationException( 550 "target does not support the forcing of a method to return early"); 551 } 552 553 validateMirrorOrNull(returnValue); 554 555 StackFrameImpl sf; 556 try { 557 sf = (StackFrameImpl)frame(0); 558 } catch (IndexOutOfBoundsException exc) { 559 throw new InvalidStackFrameException("No more frames on the stack"); 560 } 561 sf.validateStackFrame(); 562 MethodImpl meth = (MethodImpl)sf.location().method(); 563 ValueImpl convertedValue = ValueImpl.prepareForAssignment(returnValue, 564 meth.getReturnValueContainer()); 565 566 try { 567 JDWP.ThreadReference.ForceEarlyReturn.process(vm, this, convertedValue); 568 } catch (JDWPException exc) { 569 switch (exc.errorCode()) { 570 case JDWP.Error.OPAQUE_FRAME: 571 throw new NativeMethodException(); 572 case JDWP.Error.THREAD_NOT_SUSPENDED: 573 throw new IncompatibleThreadStateException( 574 "Thread not suspended"); 575 case JDWP.Error.THREAD_NOT_ALIVE: 576 throw new IncompatibleThreadStateException( 577 "Thread has not started or has finished"); 578 case JDWP.Error.NO_MORE_FRAMES: 579 throw new InvalidStackFrameException( 580 "No more frames on the stack"); 581 default: 582 throw exc.toJDIException(); 583 } 584 } 585 } 586 587 public String toString() { 588 return "instance of " + referenceType().name() + 589 "(name='" + name() + "', " + "id=" + uniqueID() + ")"; 590 } 591 592 byte typeValueKey() { 593 return JDWP.Tag.THREAD; 594 } 595 596 void addListener(ThreadListener listener) { 597 synchronized (vm.state()) { 598 listeners.add(new WeakReference<ThreadListener>(listener)); 599 } 600 } 601 602 void removeListener(ThreadListener listener) { 603 synchronized (vm.state()) { 604 Iterator<WeakReference<ThreadListener>> iter = listeners.iterator(); 605 while (iter.hasNext()) { 606 WeakReference<ThreadListener> ref = iter.next(); 607 if (listener.equals(ref.get())) { 608 iter.remove(); 609 break; 610 } 611 } 612 } 613 } 614 615 /** 616 * Propagate the thread state change information 617 * to registered listeners. 618 * Must be entered while synchronized on vm.state() 619 */ 620 private void processThreadAction(ThreadAction action) { 621 synchronized (vm.state()) { 622 Iterator<WeakReference<ThreadListener>> iter = listeners.iterator(); 623 while (iter.hasNext()) { 624 WeakReference<ThreadListener> ref = iter.next(); 625 ThreadListener listener = ref.get(); 626 if (listener != null) { 627 switch (action.id()) { 628 case ThreadAction.THREAD_RESUMABLE: 629 if (!listener.threadResumable(action)) { 630 iter.remove(); 631 } 632 break; 633 } 634 } else { 635 // Listener is unreachable; clean up 636 iter.remove(); 637 } 638 } 639 640 // Discard our local cache 641 resetLocalCache(); 642 } 643 } 644} 645