blob: 6bb9dc86d539f29666b5fed8d63b811e897bd678 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2002-2006 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package com.sun.jmx.remote.internal;
27
28import java.io.IOException;
29import java.io.NotSerializableException;
30
31import java.util.ArrayList;
32import java.util.HashMap;
33import java.util.List;
34import java.util.Map;
35import java.util.concurrent.Executor;
36
37import java.security.AccessController;
38import java.security.PrivilegedAction;
39import javax.security.auth.Subject;
40
41import javax.management.Notification;
42import javax.management.NotificationListener;
43import javax.management.NotificationFilter;
44import javax.management.ObjectName;
45import javax.management.MBeanServerNotification;
46import javax.management.InstanceNotFoundException;
47import javax.management.ListenerNotFoundException;
48
49import javax.management.remote.NotificationResult;
50import javax.management.remote.TargetedNotification;
51
52import com.sun.jmx.remote.util.ClassLogger;
53import com.sun.jmx.remote.util.EnvHelp;
54
55
56public abstract class ClientNotifForwarder {
57 public ClientNotifForwarder(Map env) {
58 this(null, env);
59 }
60
61 private static int threadId;
62
63 /* An Executor that allows at most one executing and one pending
64 Runnable. It uses at most one thread -- as soon as there is
65 no pending Runnable the thread can exit. Another thread is
66 created as soon as there is a new pending Runnable. This
67 Executor is adapted for use in a situation where each Runnable
68 usually schedules up another Runnable. On return from the
69 first one, the second one is immediately executed. So this
70 just becomes a complicated way to write a while loop, but with
71 the advantage that you can replace it with another Executor,
72 for instance one that you are using to execute a bunch of other
73 unrelated work.
74
75 You might expect that a java.util.concurrent.ThreadPoolExecutor
76 with corePoolSize=0 and maximumPoolSize=1 would have the same
77 behavior, but it does not. A ThreadPoolExecutor only creates
78 a new thread when a new task is submitted and the number of
79 existing threads is < corePoolSize. This can never happen when
80 corePoolSize=0, so new threads are never created. Surprising,
81 but there you are.
82 */
83 private static class LinearExecutor implements Executor {
84 public synchronized void execute(Runnable command) {
85 if (this.command != null)
86 throw new IllegalArgumentException("More than one command");
87 this.command = command;
88 if (thread == null) {
89 thread = new Thread() {
90 public void run() {
91 while (true) {
92 Runnable r;
93 synchronized (LinearExecutor.this) {
94 if (LinearExecutor.this.command == null) {
95 thread = null;
96 return;
97 } else {
98 r = LinearExecutor.this.command;
99 LinearExecutor.this.command = null;
100 }
101 }
102 r.run();
103 }
104 }
105 };
106 thread.setDaemon(true);
107 thread.setName("ClientNotifForwarder-" + ++threadId);
108 thread.start();
109 }
110 }
111
112 private Runnable command;
113 private Thread thread;
114 }
115
116 public ClientNotifForwarder(ClassLoader defaultClassLoader, Map env) {
117 maxNotifications = EnvHelp.getMaxFetchNotifNumber(env);
118 timeout = EnvHelp.getFetchTimeout(env);
119
120 /* You can supply an Executor in which the remote call to
121 fetchNotifications will be made. The Executor's execute
122 method reschedules another task, so you must not use
123 an Executor that executes tasks in the caller's thread. */
124 Executor ex = (Executor)
125 env.get("jmx.remote.x.fetch.notifications.executor");
126 if (ex == null)
127 ex = new LinearExecutor();
128 else if (logger.traceOn())
129 logger.trace("ClientNotifForwarder", "executor is " + ex);
130
131 this.defaultClassLoader = defaultClassLoader;
132 this.executor = ex;
133 }
134
135 /**
136 * Called to to fetch notifications from a server.
137 */
138 abstract protected NotificationResult fetchNotifs(long clientSequenceNumber,
139 int maxNotifications,
140 long timeout)
141 throws IOException, ClassNotFoundException;
142
143 abstract protected Integer addListenerForMBeanRemovedNotif()
144 throws IOException, InstanceNotFoundException;
145
146 abstract protected void removeListenerForMBeanRemovedNotif(Integer id)
147 throws IOException, InstanceNotFoundException,
148 ListenerNotFoundException;
149
150 /**
151 * Used to send out a notification about lost notifs
152 */
153 abstract protected void lostNotifs(String message, long number);
154
155
156 public synchronized void addNotificationListener(Integer listenerID,
157 ObjectName name,
158 NotificationListener listener,
159 NotificationFilter filter,
160 Object handback,
161 Subject delegationSubject)
162 throws IOException, InstanceNotFoundException {
163
164 if (logger.traceOn()) {
165 logger.trace("addNotificationListener",
166 "Add the listener "+listener+" at "+name);
167 }
168
169 infoList.put(listenerID,
170 new ClientListenerInfo(listenerID,
171 name,
172 listener,
173 filter,
174 handback,
175 delegationSubject));
176
177
178 init(false);
179 }
180
181 public synchronized Integer[]
182 removeNotificationListener(ObjectName name,
183 NotificationListener listener)
184 throws ListenerNotFoundException, IOException {
185
186 beforeRemove();
187
188 if (logger.traceOn()) {
189 logger.trace("removeNotificationListener",
190 "Remove the listener "+listener+" from "+name);
191 }
192
193 List<Integer> ids = new ArrayList<Integer>();
194 List<ClientListenerInfo> values =
195 new ArrayList<ClientListenerInfo>(infoList.values());
196 for (int i=values.size()-1; i>=0; i--) {
197 ClientListenerInfo li = values.get(i);
198
199 if (li.sameAs(name, listener)) {
200 ids.add(li.getListenerID());
201
202 infoList.remove(li.getListenerID());
203 }
204 }
205
206 if (ids.isEmpty())
207 throw new ListenerNotFoundException("Listener not found");
208
209 return ids.toArray(new Integer[0]);
210 }
211
212 public synchronized Integer
213 removeNotificationListener(ObjectName name,
214 NotificationListener listener,
215 NotificationFilter filter,
216 Object handback)
217 throws ListenerNotFoundException, IOException {
218
219 if (logger.traceOn()) {
220 logger.trace("removeNotificationListener",
221 "Remove the listener "+listener+" from "+name);
222 }
223
224 beforeRemove();
225
226 Integer id = null;
227
228 List<ClientListenerInfo> values =
229 new ArrayList<ClientListenerInfo>(infoList.values());
230 for (int i=values.size()-1; i>=0; i--) {
231 ClientListenerInfo li = values.get(i);
232 if (li.sameAs(name, listener, filter, handback)) {
233 id=li.getListenerID();
234
235 infoList.remove(id);
236
237 break;
238 }
239 }
240
241 if (id == null)
242 throw new ListenerNotFoundException("Listener not found");
243
244 return id;
245 }
246
247 public synchronized Integer[] removeNotificationListener(ObjectName name) {
248 if (logger.traceOn()) {
249 logger.trace("removeNotificationListener",
250 "Remove all listeners registered at "+name);
251 }
252
253 List<Integer> ids = new ArrayList<Integer>();
254
255 List<ClientListenerInfo> values =
256 new ArrayList<ClientListenerInfo>(infoList.values());
257 for (int i=values.size()-1; i>=0; i--) {
258 ClientListenerInfo li = values.get(i);
259 if (li.sameAs(name)) {
260 ids.add(li.getListenerID());
261
262 infoList.remove(li.getListenerID());
263 }
264 }
265
266 return ids.toArray(new Integer[0]);
267 }
268
269 /*
270 * Called when a connector is doing reconnection. Like <code>postReconnection</code>,
271 * this method is intended to be called only by a client connector:
272 * <code>RMIConnector</code> and <code>ClientIntermediary</code>.
273 * Call this method will set the flag beingReconnection to <code>true</code>,
274 * and the thread used to fetch notifis will be stopped, a new thread can be
275 * created only after the method <code>postReconnection</code> is called.
276 *
277 * It is caller's responsiblity to not re-call this method before calling
278 * <code>postReconnection</code>.
279 */
280 public synchronized ClientListenerInfo[] preReconnection() throws IOException {
281 if (state == TERMINATED || beingReconnected) { // should never
282 throw new IOException("Illegal state.");
283 }
284
285 final ClientListenerInfo[] tmp =
286 infoList.values().toArray(new ClientListenerInfo[0]);
287
288
289 beingReconnected = true;
290
291 infoList.clear();
292
293 if (currentFetchThread == Thread.currentThread()) {
294 /* we do not need to stop the fetching thread, because this thread is
295 used to do restarting and it will not be used to do fetching during
296 the re-registering the listeners.*/
297 return tmp;
298 }
299
300 while (state == STARTING) {
301 try {
302 wait();
303 } catch (InterruptedException ire) {
304 IOException ioe = new IOException(ire.toString());
305 EnvHelp.initCause(ioe, ire);
306
307 throw ioe;
308 }
309 }
310
311 if (state == STARTED) {
312 setState(STOPPING);
313 }
314
315 return tmp;
316 }
317
318 /**
319 * Called after reconnection is finished.
320 * This method is intended to be called only by a client connector:
321 * <code>RMIConnector</code> and <code>ClientIntermediary</code>.
322 */
323 public synchronized void postReconnection(ClientListenerInfo[] listenerInfos)
324 throws IOException {
325
326 if (state == TERMINATED) {
327 return;
328 }
329
330 while (state == STOPPING) {
331 try {
332 wait();
333 } catch (InterruptedException ire) {
334 IOException ioe = new IOException(ire.toString());
335 EnvHelp.initCause(ioe, ire);
336 throw ioe;
337 }
338 }
339
340 final boolean trace = logger.traceOn();
341 final int len = listenerInfos.length;
342
343 for (int i=0; i<len; i++) {
344 if (trace) {
345 logger.trace("addNotificationListeners",
346 "Add a listener at "+
347 listenerInfos[i].getListenerID());
348 }
349
350 infoList.put(listenerInfos[i].getListenerID(), listenerInfos[i]);
351 }
352
353 beingReconnected = false;
354 notifyAll();
355
356 if (currentFetchThread == Thread.currentThread()) {
357 // no need to init, simply get the id
358 try {
359 mbeanRemovedNotifID = addListenerForMBeanRemovedNotif();
360 } catch (Exception e) {
361 final String msg =
362 "Failed to register a listener to the mbean " +
363 "server: the client will not do clean when an MBean " +
364 "is unregistered";
365 if (logger.traceOn()) {
366 logger.trace("init", msg, e);
367 }
368 }
369 } else if (listenerInfos.length > 0) { // old listeners re-registered
370 init(true);
371 } else if (infoList.size() > 0) {
372 // but new listeners registered during reconnection
373 init(false);
374 }
375 }
376
377 public synchronized void terminate() {
378 if (state == TERMINATED) {
379 return;
380 }
381
382 if (logger.traceOn()) {
383 logger.trace("terminate", "Terminating...");
384 }
385
386 if (state == STARTED) {
387 infoList.clear();
388 }
389
390 setState(TERMINATED);
391 }
392
393// -------------------------------------------------
394// private classes
395// -------------------------------------------------
396 //
397 private class NotifFetcher implements Runnable {
398 public void run() {
399 synchronized (ClientNotifForwarder.this) {
400 currentFetchThread = Thread.currentThread();
401
402 if (state == STARTING)
403 setState(STARTED);
404 }
405
406 if (defaultClassLoader != null) {
407 AccessController.doPrivileged(new PrivilegedAction<Void>() {
408 public Void run() {
409 Thread.currentThread().
410 setContextClassLoader(defaultClassLoader);
411 return null;
412 }
413 });
414 }
415
416 NotificationResult nr = null;
417 if (!shouldStop() && (nr = fetchNotifs()) != null) {
418 // nr == null means got exception
419
420 final TargetedNotification[] notifs =
421 nr.getTargetedNotifications();
422 final int len = notifs.length;
423 final Map<Integer, ClientListenerInfo> listeners;
424 final Integer myListenerID;
425
426 long missed = 0;
427
428 synchronized(ClientNotifForwarder.this) {
429 // check sequence number.
430 //
431 if (clientSequenceNumber >= 0) {
432 missed = nr.getEarliestSequenceNumber() -
433 clientSequenceNumber;
434 }
435
436 clientSequenceNumber = nr.getNextSequenceNumber();
437
438 final int size = infoList.size();
439 listeners = new HashMap<Integer, ClientListenerInfo>();
440
441 for (int i = 0 ; i < len ; i++) {
442 final TargetedNotification tn = notifs[i];
443 final Integer listenerID = tn.getListenerID();
444
445 // check if an mbean unregistration notif
446 if (!listenerID.equals(mbeanRemovedNotifID)) {
447 final ClientListenerInfo li = infoList.get(listenerID);
448 if (li != null)
449 listeners.put(listenerID,li);
450 continue;
451 }
452 final Notification notif = tn.getNotification();
453 final String unreg =
454 MBeanServerNotification.UNREGISTRATION_NOTIFICATION;
455 if (notif instanceof MBeanServerNotification &&
456 notif.getType().equals(unreg)) {
457
458 MBeanServerNotification mbsn =
459 (MBeanServerNotification) notif;
460 ObjectName name = mbsn.getMBeanName();
461
462 removeNotificationListener(name);
463 }
464 }
465 myListenerID = mbeanRemovedNotifID;
466 }
467
468 if (missed > 0) {
469 final String msg =
470 "May have lost up to " + missed +
471 " notification" + (missed == 1 ? "" : "s");
472 lostNotifs(msg, missed);
473 logger.trace("NotifFetcher.run", msg);
474 }
475
476 // forward
477 for (int i = 0 ; i < len ; i++) {
478 final TargetedNotification tn = notifs[i];
479 dispatchNotification(tn,myListenerID,listeners);
480 }
481 }
482
483 synchronized (ClientNotifForwarder.this) {
484 currentFetchThread = null;
485 }
486
487 if (nr == null || shouldStop()) {
488 // tell that the thread is REALLY stopped
489 setState(STOPPED);
490 } else {
491 executor.execute(this);
492 }
493 }
494
495 void dispatchNotification(TargetedNotification tn,
496 Integer myListenerID,
497 Map<Integer, ClientListenerInfo> listeners) {
498 final Notification notif = tn.getNotification();
499 final Integer listenerID = tn.getListenerID();
500
501 if (listenerID.equals(myListenerID)) return;
502 final ClientListenerInfo li = listeners.get(listenerID);
503
504 if (li == null) {
505 logger.trace("NotifFetcher.dispatch",
506 "Listener ID not in map");
507 return;
508 }
509
510 NotificationListener l = li.getListener();
511 Object h = li.getHandback();
512 try {
513 l.handleNotification(notif, h);
514 } catch (RuntimeException e) {
515 final String msg =
516 "Failed to forward a notification " +
517 "to a listener";
518 logger.trace("NotifFetcher-run", msg, e);
519 }
520
521 }
522
523 private NotificationResult fetchNotifs() {
524 try {
525 NotificationResult nr = ClientNotifForwarder.this.
526 fetchNotifs(clientSequenceNumber,maxNotifications,
527 timeout);
528
529 if (logger.traceOn()) {
530 logger.trace("NotifFetcher-run",
531 "Got notifications from the server: "+nr);
532 }
533
534 return nr;
535 } catch (ClassNotFoundException e) {
536 logger.trace("NotifFetcher.fetchNotifs", e);
537 return fetchOneNotif();
538 } catch (NotSerializableException e) {
539 logger.trace("NotifFetcher.fetchNotifs", e);
540 return fetchOneNotif();
541 } catch (IOException ioe) {
542 if (!shouldStop()) {
543 logger.error("NotifFetcher-run",
544 "Failed to fetch notification, " +
545 "stopping thread. Error is: " + ioe, ioe);
546 logger.debug("NotifFetcher-run",ioe);
547 }
548
549 // no more fetching
550 return null;
551 }
552 }
553
554 /* Fetch one notification when we suspect that it might be a
555 notification that we can't deserialize (because of a
556 missing class). First we ask for 0 notifications with 0
557 timeout. This allows us to skip sequence numbers for
558 notifications that don't match our filters. Then we ask
559 for one notification. If that produces a
560 ClassNotFoundException or a NotSerializableException, we
561 increase our sequence number and ask again. Eventually we
562 will either get a successful notification, or a return with
563 0 notifications. In either case we can return a
564 NotificationResult. This algorithm works (albeit less
565 well) even if the server implementation doesn't optimize a
566 request for 0 notifications to skip sequence numbers for
567 notifications that don't match our filters.
568
569 If we had at least one ClassNotFoundException, then we
570 must emit a JMXConnectionNotification.LOST_NOTIFS.
571 */
572 private NotificationResult fetchOneNotif() {
573 ClientNotifForwarder cnf = ClientNotifForwarder.this;
574
575 long startSequenceNumber = clientSequenceNumber;
576
577 int notFoundCount = 0;
578
579 NotificationResult result = null;
580
581 while (result == null && !shouldStop()) {
582 NotificationResult nr;
583
584 try {
585 // 0 notifs to update startSequenceNumber
586 nr = cnf.fetchNotifs(startSequenceNumber, 0, 0L);
587 } catch (ClassNotFoundException e) {
588 logger.warning("NotifFetcher.fetchOneNotif",
589 "Impossible exception: " + e);
590 logger.debug("NotifFetcher.fetchOneNotif",e);
591 return null;
592 } catch (IOException e) {
593 if (!shouldStop())
594 logger.trace("NotifFetcher.fetchOneNotif", e);
595 return null;
596 }
597
598 if (shouldStop())
599 return null;
600
601 startSequenceNumber = nr.getNextSequenceNumber();
602
603 try {
604 // 1 notif to skip possible missing class
605 result = cnf.fetchNotifs(startSequenceNumber, 1, 0L);
606 } catch (Exception e) {
607 if (e instanceof ClassNotFoundException
608 || e instanceof NotSerializableException) {
609 logger.warning("NotifFetcher.fetchOneNotif",
610 "Failed to deserialize a notification: "+e.toString());
611 if (logger.traceOn()) {
612 logger.trace("NotifFetcher.fetchOneNotif",
613 "Failed to deserialize a notification.", e);
614 }
615
616 notFoundCount++;
617 startSequenceNumber++;
618 } else {
619 if (!shouldStop())
620 logger.trace("NotifFetcher.fetchOneNotif", e);
621 return null;
622 }
623 }
624 }
625
626 if (notFoundCount > 0) {
627 final String msg =
628 "Dropped " + notFoundCount + " notification" +
629 (notFoundCount == 1 ? "" : "s") +
630 " because classes were missing locally";
631 lostNotifs(msg, notFoundCount);
632 }
633
634 return result;
635 }
636
637 private boolean shouldStop() {
638 synchronized (ClientNotifForwarder.this) {
639 if (state != STARTED) {
640 return true;
641 } else if (infoList.size() == 0) {
642 // no more listener, stop fetching
643 setState(STOPPING);
644
645 return true;
646 }
647
648 return false;
649 }
650 }
651 }
652
653
654// -------------------------------------------------
655// private methods
656// -------------------------------------------------
657 private synchronized void setState(int newState) {
658 if (state == TERMINATED) {
659 return;
660 }
661
662 state = newState;
663 this.notifyAll();
664 }
665
666 /*
667 * Called to decide whether need to start a thread for fetching notifs.
668 * <P>The parameter reconnected will decide whether to initilize the clientSequenceNumber,
669 * initilaizing the clientSequenceNumber means to ignore all notifications arrived before.
670 * If it is reconnected, we will not initialize in order to get all notifications arrived
671 * during the reconnection. It may cause the newly registered listeners to receive some
672 * notifications arrived before its registray.
673 */
674 private synchronized void init(boolean reconnected) throws IOException {
675 switch (state) {
676 case STARTED:
677 return;
678 case STARTING:
679 return;
680 case TERMINATED:
681 throw new IOException("The ClientNotifForwarder has been terminated.");
682 case STOPPING:
683 if (beingReconnected == true) {
684 // wait for another thread to do, which is doing reconnection
685 return;
686 }
687
688 while (state == STOPPING) { // make sure only one fetching thread.
689 try {
690 wait();
691 } catch (InterruptedException ire) {
692 IOException ioe = new IOException(ire.toString());
693 EnvHelp.initCause(ioe, ire);
694
695 throw ioe;
696 }
697 }
698
699 // re-call this method to check the state again,
700 // the state can be other value like TERMINATED.
701 init(reconnected);
702
703 return;
704 case STOPPED:
705 if (beingReconnected == true) {
706 // wait for another thread to do, which is doing reconnection
707 return;
708 }
709
710 if (logger.traceOn()) {
711 logger.trace("init", "Initializing...");
712 }
713
714 // init the clientSequenceNumber if not reconnected.
715 if (!reconnected) {
716 try {
717 NotificationResult nr = fetchNotifs(-1, 0, 0);
718 clientSequenceNumber = nr.getNextSequenceNumber();
719 } catch (ClassNotFoundException e) {
720 // can't happen
721 logger.warning("init", "Impossible exception: "+ e);
722 logger.debug("init",e);
723 }
724 }
725
726 // for cleaning
727 try {
728 mbeanRemovedNotifID = addListenerForMBeanRemovedNotif();
729 } catch (Exception e) {
730 final String msg =
731 "Failed to register a listener to the mbean " +
732 "server: the client will not do clean when an MBean " +
733 "is unregistered";
734 if (logger.traceOn()) {
735 logger.trace("init", msg, e);
736 }
737 }
738
739 setState(STARTING);
740
741 // start fetching
742 executor.execute(new NotifFetcher());
743
744 return;
745 default:
746 // should not
747 throw new IOException("Unknown state.");
748 }
749 }
750
751 /**
752 * Import: should not remove a listener during reconnection, the reconnection
753 * needs to change the listener list and that will possibly make removal fail.
754 */
755 private synchronized void beforeRemove() throws IOException {
756 while (beingReconnected) {
757 if (state == TERMINATED) {
758 throw new IOException("Terminated.");
759 }
760
761 try {
762 wait();
763 } catch (InterruptedException ire) {
764 IOException ioe = new IOException(ire.toString());
765 EnvHelp.initCause(ioe, ire);
766
767 throw ioe;
768 }
769 }
770
771 if (state == TERMINATED) {
772 throw new IOException("Terminated.");
773 }
774 }
775
776// -------------------------------------------------
777// private variables
778// -------------------------------------------------
779
780 private final ClassLoader defaultClassLoader;
781 private final Executor executor;
782
783 private final Map<Integer, ClientListenerInfo> infoList =
784 new HashMap<Integer, ClientListenerInfo>();
785
786 // notif stuff
787 private long clientSequenceNumber = -1;
788 private final int maxNotifications;
789 private final long timeout;
790
791 private Integer mbeanRemovedNotifID = null;
792
793 private Thread currentFetchThread;
794
795 // admin stuff
796 private boolean inited = false;
797
798 // state
799 /**
800 * This state means that a thread is being created for fetching and forwarding notifications.
801 */
802 private static final int STARTING = 0;
803
804 /**
805 * This state tells that a thread has been started for fetching and forwarding notifications.
806 */
807 private static final int STARTED = 1;
808
809 /**
810 * This state means that the fetching thread is informed to stop.
811 */
812 private static final int STOPPING = 2;
813
814 /**
815 * This state means that the fetching thread is already stopped.
816 */
817 private static final int STOPPED = 3;
818
819 /**
820 * This state means that this object is terminated and no more thread will be created
821 * for fetching notifications.
822 */
823 private static final int TERMINATED = 4;
824
825 private int state = STOPPED;
826
827 /**
828 * This variable is used to tell whether a connector (RMIConnector or ClientIntermediary)
829 * is doing reconnection.
830 * This variable will be set to true by the method <code>preReconnection</code>, and set
831 * to false by <code>postReconnection</code>.
832 * When beingReconnected == true, no thread will be created for fetching notifications.
833 */
834 private boolean beingReconnected = false;
835
836 private static final ClassLogger logger =
837 new ClassLogger("javax.management.remote.misc",
838 "ClientNotifForwarder");
839}