1 /* 2 * Created on 22-Sep-2004 3 * Created by Paul Gardner 4 * Copyright (C) Azureus Software, Inc, All Rights Reserved. 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License 8 * as published by the Free Software Foundation; either version 2 9 * of the License, or (at your option) any later version. 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * You should have received a copy of the GNU General Public License 15 * along with this program; if not, write to the Free Software 16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 17 * 18 */ 19 20 package org.gudy.azureus2.core3.util; 21 22 import java.util.*; 23 24 /** 25 * @author parg 26 * 27 */ 28 29 public abstract class 30 AEMonSem 31 { 32 protected static boolean DEBUG = AEDiagnostics.DEBUG_MONITOR_SEM_USAGE; 33 protected static boolean DEBUG_CHECK_DUPLICATES = false; 34 35 protected static long DEBUG_TIMER = 30000; 36 37 private static ThreadLocal tls = 38 new ThreadLocal() 39 { 40 public Object 41 initialValue() 42 { 43 return( new Stack()); 44 } 45 }; 46 47 private static long monitor_id_next; 48 private static long semaphore_id_next; 49 50 private static Map debug_traces = new HashMap(); 51 private static List debug_recursions = new ArrayList(); 52 private static List debug_reciprocals = new ArrayList(); 53 //private static List debug_sem_in_mon = new ArrayList(); 54 55 56 57 private static Map debug_name_mapping = new WeakHashMap(); 58 private static Map debug_monitors = new WeakHashMap(); 59 private static Map debug_semaphores = new WeakHashMap(); 60 61 static{ 62 if ( DEBUG ){ 63 64 // defer this due to initialisation problems 65 66 Thread t = new Thread( "AEMonSem:delay debug init" ) 67 { 68 public void 69 run() 70 { 71 // add known and validated exceptions 72 73 debug_recursions.add( "ResourceDownloader" ); // known tree recursion 74 debug_recursions.add( "ConnectionPool:CP" ); // known tree recursion 75 debug_recursions.add( "(S)RDRretry" ); // RDretry sem left on stack after 1st d/l so appears recursive on subsequent 76 77 try{ 78 Thread.sleep(DEBUG_TIMER); 79 80 }catch( Throwable e ){ 81 } 82 83 TimerEventPerformer performer = 84 new TimerEventPerformer() 85 { 86 AEDiagnosticsLogger diag_logger; 87 88 public void 89 perform( 90 TimerEvent event ) 91 { 92 if ( diag_logger == null ){ 93 94 diag_logger = AEDiagnostics.getLogger( "monsem" ); 95 } 96 97 check( diag_logger ); 98 } 99 }; 100 101 performer.perform( null ); 102 103 new Timer("AEMonSem").addPeriodicEvent( DEBUG_TIMER, performer ); 104 } 105 }; 106 107 t.setDaemon( true ); 108 t.start()109 t.start(); 110 } 111 } 112 113 protected static void check( AEDiagnosticsLogger diag_logger )114 check( 115 AEDiagnosticsLogger diag_logger ) 116 { 117 List active = new ArrayList(); 118 List waiting_monitors = new ArrayList(); 119 List busy_monitors = new ArrayList(); 120 List waiting_semaphores = new ArrayList(); 121 122 synchronized( AEMonSem.class ){ 123 124 // dumpTrace(); 125 126 diag_logger.log( 127 "AEMonSem: mid = " + monitor_id_next + 128 ", sid = " + semaphore_id_next + 129 ", monitors = " + debug_monitors.size() + 130 ", semaphores = " + debug_semaphores.size() + 131 ", names = " + debug_name_mapping.size() + 132 ", traces = " + debug_traces.size()); 133 134 135 Iterator it = debug_monitors.keySet().iterator(); 136 137 long new_mon_entries = 0; 138 139 while (it.hasNext()){ 140 141 AEMonitor monitor = (AEMonitor)it.next(); 142 143 long diff = monitor.entry_count - monitor.last_entry_count; 144 145 if ( diff != 0 ){ 146 147 active.add( monitor ); 148 149 new_mon_entries += diff; 150 } 151 152 if (monitor.waiting > 0 ){ 153 154 waiting_monitors.add( monitor ); 155 156 }else if ( monitor.owner != null ){ 157 158 busy_monitors.add( monitor ); 159 } 160 } 161 162 it = debug_semaphores.keySet().iterator(); 163 164 long new_sem_entries = 0; 165 166 while (it.hasNext()){ 167 168 AEMonSem semaphore = (AEMonSem)it.next(); 169 170 long diff = semaphore.entry_count - semaphore.last_entry_count; 171 172 if ( diff != 0 ){ 173 174 active.add( semaphore ); 175 176 new_sem_entries += diff; 177 } 178 179 if (semaphore.waiting > 0 ){ 180 181 waiting_semaphores.add( semaphore ); 182 } 183 } 184 185 diag_logger.log( 186 " activity: monitors = " + new_mon_entries + " - " + (new_mon_entries / (DEBUG_TIMER/1000)) + 187 "/sec, semaphores = " + new_sem_entries + " - " + 188 (new_sem_entries / (DEBUG_TIMER/1000)) + "/sec "); 189 } 190 191 AEMonSem[] x = new AEMonSem[active.size()]; 192 193 active.toArray(x); 194 195 // sort by name and merge values 196 197 Arrays.sort( 198 x, 199 new Comparator() 200 { 201 public int 202 compare( 203 Object o1, 204 Object o2 ) 205 { 206 AEMonSem a1 = (AEMonSem)o1; 207 AEMonSem a2 = (AEMonSem)o2; 208 209 return( a1.name.compareTo( a2.name )); 210 } 211 212 }); 213 214 AEMonSem current = null; 215 long current_total = 0; 216 217 Object[][] total_x = new Object[x.length][]; 218 219 int total_pos = 0; 220 221 for (int i=0;i<x.length;i++){ 222 223 AEMonSem ms = x[i]; 224 225 long diff = ms.entry_count - ms.last_entry_count; 226 227 if ( current == null ){ 228 229 current = ms; 230 231 }else{ 232 233 if( current.name.equals( ms.name )){ 234 235 current_total += diff; 236 237 }else{ 238 total_x[total_pos++] = new Object[]{ current.name, new Long( current_total )}; 239 240 current = ms; 241 current_total = diff; 242 } 243 } 244 } 245 246 if (current != null ){ 247 248 total_x[total_pos++] = new Object[]{ current.name, new Long( current_total )}; 249 } 250 251 Arrays.sort( 252 total_x, 253 new Comparator() 254 { 255 public int 256 compare( 257 Object o1, 258 Object o2 ) 259 { 260 Object[] a1 = (Object[])o1; 261 Object[] a2 = (Object[])o2; 262 263 if ( a1 == null && a2 == null){ 264 265 return(0); 266 267 }else if ( a1 == null ){ 268 269 return( 1 ); 270 271 }else if ( a2 == null ){ 272 273 return( -1 ); 274 } 275 276 long a1_count = ((Long)a1[1]).longValue(); 277 long a2_count = ((Long)a2[1]).longValue(); 278 279 return((int)(a2_count - a1_count )); 280 } 281 282 }); 283 284 285 286 287 String top_act_str = " top activity: "; 288 289 for (int i=0;i<Math.min(10,total_x.length);i++){ 290 291 if ( total_x[i] != null ){ 292 293 top_act_str += (i==0?"":", ") + total_x[i][0] + " = " + (total_x[i][1]); 294 } 295 } 296 297 diag_logger.log( top_act_str ); 298 299 if ( waiting_monitors.size() > 0 ){ 300 301 diag_logger.log( " waiting monitors" ); 302 303 for (int i=0;i<waiting_monitors.size();i++){ 304 305 AEMonSem ms = (AEMonSem)waiting_monitors.get(i); 306 307 Thread last_waiter = ((AEMonitor)ms).last_waiter; 308 309 diag_logger.log( " [" + (last_waiter==null?"<waiter lost>":last_waiter.getName()) + "] " + ms.name + " - " + ms.last_trace_key ); 310 } 311 } 312 313 if ( busy_monitors.size() > 0 ){ 314 315 diag_logger.log( " busy monitors" ); 316 317 for (int i=0;i<busy_monitors.size();i++){ 318 319 AEMonSem ms = (AEMonSem)busy_monitors.get(i); 320 321 Thread owner = ((AEMonitor)ms).owner; 322 323 diag_logger.log( " [" + (owner==null?"<owner lost>":owner.getName()) + "] " + ms.name + " - " + ms.last_trace_key ); 324 } 325 } 326 327 if ( waiting_semaphores.size() > 0 ){ 328 329 diag_logger.log( " waiting semaphores" ); 330 331 for (int i=0;i<waiting_semaphores.size();i++){ 332 333 AEMonSem ms = (AEMonSem)waiting_semaphores.get(i); 334 335 Thread last_waiter = ((AESemaphore)ms).latest_waiter; 336 337 diag_logger.log( " [" + (last_waiter==null?"<waiter lost>":last_waiter.getName()) + "] " + ms.name + " - " + ms.last_trace_key ); 338 } 339 } 340 341 for (int i=0;i<x.length;i++){ 342 343 AEMonSem ms = x[i]; 344 345 ms.last_entry_count = ms.entry_count; 346 } 347 } 348 349 350 protected long entry_count; 351 protected long last_entry_count; 352 protected String last_trace_key; 353 354 355 protected String name; 356 protected boolean is_monitor; 357 protected int waiting = 0; 358 359 protected AEMonSem( String _name, boolean _monitor )360 AEMonSem( 361 String _name, 362 boolean _monitor ) 363 { 364 is_monitor = _monitor; 365 366 if ( is_monitor) { 367 368 name = _name; 369 }else{ 370 371 name = StringInterner.intern("(S)" + _name); 372 } 373 374 if ( DEBUG ){ 375 376 synchronized( AEMonSem.class ){ 377 378 if ( is_monitor ){ 379 monitor_id_next++; 380 }else{ 381 semaphore_id_next++; 382 } 383 384 StackTraceElement elt = new Exception().getStackTrace()[2]; 385 386 String class_name = elt.getClassName(); 387 int line_number = elt.getLineNumber(); 388 389 monSemData new_entry = new monSemData( class_name, line_number); 390 391 if ( is_monitor ){ 392 393 debug_monitors.put( this, new_entry ); 394 395 }else{ 396 397 debug_semaphores.put( this, new_entry ); 398 } 399 400 if ( DEBUG_CHECK_DUPLICATES ){ 401 402 monSemData existing_name_entry = (monSemData)debug_name_mapping.get( name ); 403 404 if ( existing_name_entry == null ){ 405 406 debug_name_mapping.put( name, new_entry ); 407 408 }else{ 409 410 if ( ( !existing_name_entry.class_name.equals( class_name )) || 411 existing_name_entry.line_number != line_number ){ 412 413 Debug.out( new Exception("Duplicate AEMonSem name '" + name + "'")); 414 } 415 } 416 } 417 } 418 } 419 } 420 421 protected void debugEntry()422 debugEntry() 423 { 424 /* 425 if ( trace ){ 426 traceEntry(); 427 } 428 */ 429 430 try{ 431 // bad things are: 432 // A->B and somewhere else B->A 433 // or 434 // A(inst1) -> A(inst2) 435 436 Stack stack = (Stack)tls.get(); 437 438 if ( stack.size() > 64 ){ 439 440 StringBuffer sb = new StringBuffer(1024); 441 442 for (int i=0;i<stack.size();i++){ 443 444 AEMonSem mon = (AEMonSem)stack.get(i); 445 446 sb.append( "$" + mon.name ); 447 } 448 449 Debug.out( "**** Whoaaaaaa, AEMonSem debug stack is getting too large!!!! **** " + sb ); 450 } 451 452 if ( !stack.isEmpty()){ 453 454 String recursion_trace = ""; 455 456 /* not very useful 457 if ( (!is_monitor) && 458 ((AEMonSem)stack.peek()).is_monitor ){ 459 460 if ( !debug_sem_in_mon.contains( name )){ 461 462 recursion_trace += ( recursion_trace.length()==0?"":"\r\n" ) + 463 "Semaphore reservation while holding a monitor: sem = " + name+ ", mon = " + ((AEMonSem)stack.peek()).name; 464 465 debug_sem_in_mon.add( name ); 466 } 467 } 468 */ 469 470 StringBuffer sb = new StringBuffer(); 471 472 // not very interesting for semaphores as these tend to get left on stack traces when 473 // asymetric usage (which is often) 474 475 boolean check_recursion = is_monitor && !debug_recursions.contains( name ); 476 477 String prev_name = null; 478 479 for (int i=0;i<stack.size();i++){ 480 481 AEMonSem mon = (AEMonSem)stack.get(i); 482 483 if ( check_recursion ){ 484 if ( mon.name.equals( name ) && 485 mon != this ){ 486 487 recursion_trace += 488 ( recursion_trace.length()==0?"":"\r\n" ) + 489 "Recursive locks on different instances: " + name; 490 491 debug_recursions.add( name ); 492 } 493 } 494 495 // remove consecutive duplicates 496 497 if ( prev_name == null || !mon.name.equals( prev_name )){ 498 499 sb.append("$"); 500 sb.append(mon.name); 501 } 502 503 prev_name = mon.name; 504 } 505 506 sb.append( "$" ); 507 sb.append( name ); 508 sb.append( "$" ); 509 510 String trace_key = sb.toString(); 511 512 if ( recursion_trace.length() > 0 ){ 513 514 Debug.outNoStack( recursion_trace + "\r\n " + trace_key ); 515 } 516 517 last_trace_key = trace_key; 518 519 if ( !is_monitor ){ 520 521 // only add semaphores to the stack if they aren't already present. 522 // This is because we can reserve a semaphore on one thread and 523 // release it on another. This will grow the stack indefinitely 524 525 boolean match = false; 526 527 for (int i=0;i<stack.size();i++){ 528 529 AEMonSem ms = (AEMonSem)stack.get(i); 530 531 if ( ms.name.equals( name )){ 532 533 match = true; 534 535 break; 536 } 537 } 538 539 if ( !match ){ 540 541 stack.push( this ); 542 } 543 }else{ 544 545 stack.push( this ); 546 } 547 548 synchronized( debug_traces ){ 549 550 if ( debug_traces.get(trace_key) == null ){ 551 552 Thread thread = Thread.currentThread(); 553 554 String thread_name = thread.getName() + "[" + thread.hashCode() + "]"; 555 556 String stack_trace = Debug.getStackTrace(true, false); 557 558 Iterator it = debug_traces.keySet().iterator(); 559 560 while( it.hasNext()){ 561 562 String old_key = (String)it.next(); 563 564 String[] data = (String[])debug_traces.get(old_key); 565 566 String old_thread_name = data[0]; 567 String old_trace = data[1]; 568 569 // if identical thread then we can ignore this as 570 // it can't happen concurrently 571 572 if ( thread_name.equals( old_thread_name )){ 573 574 continue; 575 } 576 577 // find the earliest occurrence of a common monitor - no point in searching 578 // beyond it 579 // e.g. a -> b -> c -> g 580 // x -> y -> b -> z 581 // stop at b because beyond this things are "protected" 582 583 584 int earliest_common = stack.size(); 585 int common_count = 0; 586 587 for (int i=0;i<stack.size();i++){ 588 589 String n1 = ((AEMonSem)stack.get(i)).name; 590 591 int p1 = old_key.indexOf( "$" + n1 + "$"); 592 593 if ( p1 != -1 ){ 594 595 common_count++; 596 597 earliest_common = Math.min( earliest_common, i+1 ); 598 } 599 } 600 601 // need at least 2 common monitors for chance of deadlock 602 603 if ( common_count >= 2 ){ 604 605 for (int i=0;i<earliest_common;i++){ 606 607 AEMonSem ms1 = (AEMonSem)stack.get(i); 608 609 if ( !ms1.is_monitor ){ 610 611 continue; 612 } 613 614 String n1 = ms1.name; 615 616 for (int j=i+1;j<stack.size();j++){ 617 618 AEMonSem ms2 = (AEMonSem)stack.get(j); 619 620 if ( !ms2.is_monitor ){ 621 622 continue; 623 } 624 625 String n2 = ms2.name; 626 627 // same object recursion already tested above 628 629 if ( !n1.equals( n2 )){ 630 631 int p1 = old_key.indexOf( "$" + n1 + "$"); 632 int p2 = old_key.indexOf( "$" + n2 + "$"); 633 634 if ( p1 != -1 && p2 != -1 && p1 > p2 ){ 635 636 String reciprocal_log = trace_key + " / " + old_key; 637 638 if ( !debug_reciprocals.contains( reciprocal_log )){ 639 640 debug_reciprocals.add( reciprocal_log ); 641 642 Debug.outNoStack( 643 "AEMonSem: Reciprocal usage:\r\n" + 644 " " + trace_key + "\r\n" + 645 " [" + thread_name + "] " + stack_trace + "\r\n" + 646 " " + old_key + "\r\n" + 647 " [" + old_thread_name + "] " + old_trace ); 648 } 649 } 650 } 651 } 652 } 653 } 654 } 655 656 debug_traces.put( trace_key, new String[]{ thread_name, stack_trace }); 657 658 // look through all the traces for an A->B and B->A 659 } 660 } 661 662 }else{ 663 664 last_trace_key = "$" + name + "$"; 665 666 stack.push( this ); 667 668 } 669 }catch( Throwable e ){ 670 671 try{ 672 Debug.printStackTrace(e); 673 674 }catch( Throwable f ){ 675 676 } 677 } 678 } 679 680 protected void debugExit()681 debugExit() 682 { 683 try{ 684 Stack stack = (Stack)tls.get(); 685 686 if ( is_monitor ){ 687 688 // skip over any sem reserves within a sync block 689 690 while( stack.peek() != this ){ 691 692 stack.pop(); 693 } 694 695 stack.pop(); 696 697 }else{ 698 699 // for semaphores we can release stuff without a matching reserve if 700 // the semaphore has an initial value or if we have one thread releasing 701 // a semaphore and another reserving it 702 703 if ( !stack.isEmpty()){ 704 705 if ( stack.peek() == this ){ 706 707 stack.pop(); 708 } 709 } 710 } 711 }catch( Throwable e ){ 712 713 try{ 714 Debug.printStackTrace(e); 715 716 }catch( Throwable f ){ 717 718 } 719 } 720 } 721 722 /* 723 protected boolean trace; 724 protected static Map trace_map = new HashMap(); 725 726 public void 727 trace( 728 boolean _on ) 729 { 730 trace = _on; 731 } 732 733 protected void 734 traceEntry() 735 { 736 String str = Debug.getCompressedStackTrace(); 737 738 synchronized( trace_map ){ 739 Map map = (Map)trace_map.get( name ); 740 if ( map == null ){ 741 map = new HashMap(); 742 trace_map.put( name, map ); 743 } 744 Long l = (Long)map.get(str); 745 746 if ( l == null ){ 747 l = new Long(1); 748 }else{ 749 l = new Long(l.longValue()+1); 750 } 751 map.put(str,l); 752 } 753 } 754 755 protected static void 756 dumpTrace() 757 { 758 synchronized( trace_map ){ 759 760 Iterator it = trace_map.entrySet().iterator(); 761 762 while( it.hasNext()){ 763 764 Map.Entry entry = (Map.Entry)it.next(); 765 766 System.out.println( entry.getKey()); 767 768 Map map = (Map)entry.getValue(); 769 770 Iterator it2 = map.entrySet().iterator(); 771 772 while( it2.hasNext()){ 773 774 Map.Entry entry2 = (Map.Entry)it2.next(); 775 776 System.out.println( " " + entry2.getValue() + " -> " + entry2.getKey()); 777 } 778 } 779 780 trace_map.clear(); 781 } 782 } 783 */ 784 785 public String getName()786 getName() 787 { 788 return( name ); 789 } 790 791 protected static class 792 monSemData 793 { 794 protected String class_name; 795 protected int line_number; 796 797 798 protected monSemData( String _class_name, int _line_number )799 monSemData( 800 String _class_name, 801 int _line_number ) 802 { 803 class_name = _class_name; 804 line_number = _line_number; 805 } 806 } 807 } 808