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