/* * Created on 22-Sep-2004 * Created by Paul Gardner * Copyright (C) 2004, 2005, 2006 Aelitis, All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * AELITIS, SAS au capital de 46,603.30 euros * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France. * */ package org.gudy.azureus2.core3.util; import java.util.*; /** * @author parg * */ public abstract class AEMonSem { // protected static boolean DEBUG = AEDiagnostics.DEBUG_MONITOR_SEM_USAGE; protected static boolean DEBUG_CHECK_DUPLICATES = false; protected static long DEBUG_TIMER = 30000; private static ThreadLocal tls = new ThreadLocal() { public Object initialValue() { return( new Stack()); } }; private static long monitor_id_next; private static long semaphore_id_next; private static Map debug_traces = new HashMap(); private static List debug_recursions = new ArrayList(); private static List debug_reciprocals = new ArrayList(); //private static List debug_sem_in_mon = new ArrayList(); private static Map debug_name_mapping = new WeakHashMap(); private static Map debug_monitors = new WeakHashMap(); private static Map debug_semaphores = new WeakHashMap(); static{ // if ( DEBUG ){ // defer this due to initialisation problems Thread t = new Thread( "AEMonSem:delay debug init" ) { public void run() { // add known and validated exceptions debug_recursions.add( "ResourceDownloader" ); // known tree recursion debug_recursions.add( "ConnectionPool:CP" ); // known tree recursion debug_recursions.add( "(S)RDRretry" ); // RDretry sem left on stack after 1st d/l so appears recursive on subsequent try{ Thread.sleep(DEBUG_TIMER); }catch( Throwable e ){ } // TimerEventPerformer performer = // new TimerEventPerformer() // { // AEDiagnosticsLogger diag_logger; // // public void // perform( // TimerEvent event ) // { // if ( diag_logger == null ){ // // diag_logger = AEDiagnostics.getLogger( "monsem" ); // } // // check( diag_logger ); // } // }; // // performer.perform( null ); // // new Timer("AEMonSem").addPeriodicEvent( DEBUG_TIMER, performer ); } }; t.setDaemon( true ); t.start(); // } } // protected static void // check( // AEDiagnosticsLogger diag_logger ) // { // List active = new ArrayList(); // List waiting_monitors = new ArrayList(); // List busy_monitors = new ArrayList(); // List waiting_semaphores = new ArrayList(); // // synchronized( AEMonSem.class ){ // // // dumpTrace(); // // diag_logger.log( // "AEMonSem: mid = " + monitor_id_next + // ", sid = " + semaphore_id_next + // ", monitors = " + debug_monitors.size() + // ", semaphores = " + debug_semaphores.size() + // ", names = " + debug_name_mapping.size() + // ", traces = " + debug_traces.size()); // // // Iterator it = debug_monitors.keySet().iterator(); // // long new_mon_entries = 0; // // while (it.hasNext()){ // // AEMonitor monitor = (AEMonitor)it.next(); // // long diff = monitor.entry_count - monitor.last_entry_count; // // if ( diff != 0 ){ // // active.add( monitor ); // // new_mon_entries += diff; // } // // if (monitor.waiting > 0 ){ // // waiting_monitors.add( monitor ); // // }else if ( monitor.owner != null ){ // // busy_monitors.add( monitor ); // } // } // // it = debug_semaphores.keySet().iterator(); // // long new_sem_entries = 0; // // while (it.hasNext()){ // // AEMonSem semaphore = (AEMonSem)it.next(); // // long diff = semaphore.entry_count - semaphore.last_entry_count; // // if ( diff != 0 ){ // // active.add( semaphore ); // // new_sem_entries += diff; // } // // if (semaphore.waiting > 0 ){ // // waiting_semaphores.add( semaphore ); // } // } // // diag_logger.log( // " activity: monitors = " + new_mon_entries + " - " + (new_mon_entries / (DEBUG_TIMER/1000)) + // "/sec, semaphores = " + new_sem_entries + " - " + // (new_sem_entries / (DEBUG_TIMER/1000)) + "/sec "); // } // // AEMonSem[] x = new AEMonSem[active.size()]; // // active.toArray(x); // // // sort by name and merge values // // Arrays.sort( // x, // new Comparator() // { // public int // compare( // Object o1, // Object o2 ) // { // AEMonSem a1 = (AEMonSem)o1; // AEMonSem a2 = (AEMonSem)o2; // // return( a1.name.compareTo( a2.name )); // } // // }); // // AEMonSem current = null; // long current_total = 0; // // Object[][] total_x = new Object[x.length][]; // // int total_pos = 0; // // for (int i=0;i<x.length;i++){ // // AEMonSem ms = x[i]; // // long diff = ms.entry_count - ms.last_entry_count; // // if ( current == null ){ // // current = ms; // // }else{ // // if( current.name.equals( ms.name )){ // // current_total += diff; // // }else{ // total_x[total_pos++] = new Object[]{ current.name, new Long( current_total )}; // // current = ms; // current_total = diff; // } // } // } // // if (current != null ){ // // total_x[total_pos++] = new Object[]{ current.name, new Long( current_total )}; // } // // Arrays.sort( // total_x, // new Comparator() // { // public int // compare( // Object o1, // Object o2 ) // { // Object[] a1 = (Object[])o1; // Object[] a2 = (Object[])o2; // // if ( a1 == null && a2 == null){ // // return(0); // // }else if ( a1 == null ){ // // return( 1 ); // // }else if ( a2 == null ){ // // return( -1 ); // } // // long a1_count = ((Long)a1[1]).longValue(); // long a2_count = ((Long)a2[1]).longValue(); // // return((int)(a2_count - a1_count )); // } // // }); // // // // // String top_act_str = " top activity: "; // // for (int i=0;i<Math.min(10,total_x.length);i++){ // // if ( total_x[i] != null ){ // // top_act_str += (i==0?"":", ") + total_x[i][0] + " = " + (total_x[i][1]); // } // } // // diag_logger.log( top_act_str ); // // if ( waiting_monitors.size() > 0 ){ // // diag_logger.log( " waiting monitors" ); // // for (int i=0;i<waiting_monitors.size();i++){ // // AEMonSem ms = (AEMonSem)waiting_monitors.get(i); // // Thread last_waiter = ((AEMonitor)ms).last_waiter; // // diag_logger.log( " [" + (last_waiter==null?"<waiter lost>":last_waiter.getName()) + "] " + ms.name + " - " + ms.last_trace_key ); // } // } // // if ( busy_monitors.size() > 0 ){ // // diag_logger.log( " busy monitors" ); // // for (int i=0;i<busy_monitors.size();i++){ // // AEMonSem ms = (AEMonSem)busy_monitors.get(i); // // Thread owner = ((AEMonitor)ms).owner; // // diag_logger.log( " [" + (owner==null?"<owner lost>":owner.getName()) + "] " + ms.name + " - " + ms.last_trace_key ); // } // } // // if ( waiting_semaphores.size() > 0 ){ // // diag_logger.log( " waiting semaphores" ); // // for (int i=0;i<waiting_semaphores.size();i++){ // // AEMonSem ms = (AEMonSem)waiting_semaphores.get(i); // // Thread last_waiter = ((AESemaphore)ms).latest_waiter; // // diag_logger.log( " [" + (last_waiter==null?"<waiter lost>":last_waiter.getName()) + "] " + ms.name + " - " + ms.last_trace_key ); // } // } // // for (int i=0;i<x.length;i++){ // // AEMonSem ms = x[i]; // // ms.last_entry_count = ms.entry_count; // } // } protected long entry_count; protected long last_entry_count; protected String last_trace_key; protected String name; protected boolean is_monitor; protected int waiting = 0; protected AEMonSem( String _name, boolean _monitor ) { is_monitor = _monitor; if ( is_monitor) { name = _name; // }else{ // //// name = StringInterner.intern("(S)" + _name); // } // if ( DEBUG ){ synchronized( AEMonSem.class ){ if ( is_monitor ){ monitor_id_next++; }else{ semaphore_id_next++; } StackTraceElement elt = new Exception().getStackTrace()[2]; String class_name = elt.getClassName(); int line_number = elt.getLineNumber(); monSemData new_entry = new monSemData( class_name, line_number); if ( is_monitor ){ debug_monitors.put( this, new_entry ); }else{ debug_semaphores.put( this, new_entry ); } if ( DEBUG_CHECK_DUPLICATES ){ monSemData existing_name_entry = (monSemData)debug_name_mapping.get( name ); if ( existing_name_entry == null ){ debug_name_mapping.put( name, new_entry ); }else{ if ( ( !existing_name_entry.class_name.equals( class_name )) || existing_name_entry.line_number != line_number ){ // Debug.out( new Exception("Duplicate AEMonSem name '" + name + "'")); } } } } } } protected void debugEntry() { /* if ( trace ){ traceEntry(); } */ try{ // bad things are: // A->B and somewhere else B->A // or // A(inst1) -> A(inst2) Stack stack = (Stack)tls.get(); if ( stack.size() > 64 ){ StringBuffer sb = new StringBuffer(1024); for (int i=0;i<stack.size();i++){ AEMonSem mon = (AEMonSem)stack.get(i); sb.append( "$" + mon.name ); } // Debug.out( "**** Whoaaaaaa, AEMonSem debug stack is getting too large!!!! **** " + sb ); } if ( !stack.isEmpty()){ String recursion_trace = ""; /* not very useful if ( (!is_monitor) && ((AEMonSem)stack.peek()).is_monitor ){ if ( !debug_sem_in_mon.contains( name )){ recursion_trace += ( recursion_trace.length()==0?"":"\r\n" ) + "Semaphore reservation while holding a monitor: sem = " + name+ ", mon = " + ((AEMonSem)stack.peek()).name; debug_sem_in_mon.add( name ); } } */ StringBuffer sb = new StringBuffer(); // not very interesting for semaphores as these tend to get left on stack traces when // asymetric usage (which is often) boolean check_recursion = is_monitor && !debug_recursions.contains( name ); String prev_name = null; for (int i=0;i<stack.size();i++){ AEMonSem mon = (AEMonSem)stack.get(i); if ( check_recursion ){ if ( mon.name.equals( name ) && mon != this ){ recursion_trace += ( recursion_trace.length()==0?"":"\r\n" ) + "Recursive locks on different instances: " + name; debug_recursions.add( name ); } } // remove consecutive duplicates if ( prev_name == null || !mon.name.equals( prev_name )){ sb.append("$"); sb.append(mon.name); } prev_name = mon.name; } sb.append( "$" ); sb.append( name ); sb.append( "$" ); String trace_key = sb.toString(); if ( recursion_trace.length() > 0 ){ // Debug.outNoStack( recursion_trace + "\r\n " + trace_key ); } last_trace_key = trace_key; if ( !is_monitor ){ // only add semaphores to the stack if they aren't already present. // This is because we can reserve a semaphore on one thread and // release it on another. This will grow the stack indefinitely boolean match = false; for (int i=0;i<stack.size();i++){ AEMonSem ms = (AEMonSem)stack.get(i); if ( ms.name.equals( name )){ match = true; break; } } if ( !match ){ stack.push( this ); } }else{ stack.push( this ); } synchronized( debug_traces ){ if ( debug_traces.get(trace_key) == null ){ Thread thread = Thread.currentThread(); String thread_name = thread.getName() + "[" + thread.hashCode() + "]"; // String stack_trace = Debug.getStackTrace(true, false); Iterator it = debug_traces.keySet().iterator(); while( it.hasNext()){ String old_key = (String)it.next(); String[] data = (String[])debug_traces.get(old_key); String old_thread_name = data[0]; String old_trace = data[1]; // if identical thread then we can ignore this as // it can't happen concurrently if ( thread_name.equals( old_thread_name )){ continue; } // find the earliest occurrence of a common monitor - no point in searching // beyond it // e.g. a -> b -> c -> g // x -> y -> b -> z // stop at b because beyond this things are "protected" int earliest_common = stack.size(); int common_count = 0; for (int i=0;i<stack.size();i++){ String n1 = ((AEMonSem)stack.get(i)).name; int p1 = old_key.indexOf( "$" + n1 + "$"); if ( p1 != -1 ){ common_count++; earliest_common = Math.min( earliest_common, i+1 ); } } // need at least 2 common monitors for chance of deadlock if ( common_count >= 2 ){ for (int i=0;i<earliest_common;i++){ AEMonSem ms1 = (AEMonSem)stack.get(i); if ( !ms1.is_monitor ){ continue; } String n1 = ms1.name; for (int j=i+1;j<stack.size();j++){ AEMonSem ms2 = (AEMonSem)stack.get(j); if ( !ms2.is_monitor ){ continue; } String n2 = ms2.name; // same object recursion already tested above if ( !n1.equals( n2 )){ int p1 = old_key.indexOf( "$" + n1 + "$"); int p2 = old_key.indexOf( "$" + n2 + "$"); if ( p1 != -1 && p2 != -1 && p1 > p2 ){ String reciprocal_log = trace_key + " / " + old_key; if ( !debug_reciprocals.contains( reciprocal_log )){ debug_reciprocals.add( reciprocal_log ); // Debug.outNoStack( // "AEMonSem: Reciprocal usage:\r\n" + // " " + trace_key + "\r\n" + // " [" + thread_name + "] " + stack_trace + "\r\n" + // " " + old_key + "\r\n" + // " [" + old_thread_name + "] " + old_trace ); } } } } } } } // debug_traces.put( trace_key, new String[]{ thread_name, stack_trace }); // look through all the traces for an A->B and B->A } } }else{ last_trace_key = "$" + name + "$"; stack.push( this ); } }catch( Throwable e ){ try{ // Debug.printStackTrace(e); }catch( Throwable f ){ } } } protected void debugExit() { try{ Stack stack = (Stack)tls.get(); if ( is_monitor ){ // skip over any sem reserves within a sync block while( stack.peek() != this ){ stack.pop(); } stack.pop(); }else{ // for semaphores we can release stuff without a matching reserve if // the semaphore has an initial value or if we have one thread releasing // a semaphore and another reserving it if ( !stack.isEmpty()){ if ( stack.peek() == this ){ stack.pop(); } } } }catch( Throwable e ){ try{ // Debug.printStackTrace(e); }catch( Throwable f ){ } } } /* protected boolean trace; protected static Map trace_map = new HashMap(); public void trace( boolean _on ) { trace = _on; } protected void traceEntry() { String str = Debug.getCompressedStackTrace(); synchronized( trace_map ){ Map map = (Map)trace_map.get( name ); if ( map == null ){ map = new HashMap(); trace_map.put( name, map ); } Long l = (Long)map.get(str); if ( l == null ){ l = new Long(1); }else{ l = new Long(l.longValue()+1); } map.put(str,l); } } protected static void dumpTrace() { synchronized( trace_map ){ Iterator it = trace_map.entrySet().iterator(); while( it.hasNext()){ Map.Entry entry = (Map.Entry)it.next(); System.out.println( entry.getKey()); Map map = (Map)entry.getValue(); Iterator it2 = map.entrySet().iterator(); while( it2.hasNext()){ Map.Entry entry2 = (Map.Entry)it2.next(); System.out.println( " " + entry2.getValue() + " -> " + entry2.getKey()); } } trace_map.clear(); } } */ public String getName() { return( name ); } protected static class monSemData { protected String class_name; protected int line_number; protected monSemData( String _class_name, int _line_number ) { class_name = _class_name; line_number = _line_number; } } }