/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/** A circular buffer of notifications received from an MBean server. */
/*
There is one instance of ArrayNotificationBuffer for every
MBeanServer object that has an attached ConnectorServer. Then, for
every ConnectorServer attached to a given MBeanServer, there is an
instance of the inner class ShareBuffer. So for example with two
ConnectorServers it looks like this:
ConnectorServer1 -> ShareBuffer1 -\
}-> ArrayNotificationBuffer
ConnectorServer2 -> ShareBuffer2 -/ |
|
v
MBeanServer
The ArrayNotificationBuffer has a circular buffer of
NamedNotification objects. Each ConnectorServer defines a
notification buffer size, and this size is recorded by the
corresponding ShareBuffer. The buffer size of the
ArrayNotificationBuffer is the maximum of all of its ShareBuffers.
When a ShareBuffer is added or removed, the ArrayNotificationBuffer
size is adjusted accordingly.
An ArrayNotificationBuffer also has a BufferListener (which is a
NotificationListener) registered on every NotificationBroadcaster
MBean in the MBeanServer to which it is attached. The cost of this
potentially large set of listeners is the principal motivation for
sharing the ArrayNotificationBuffer between ConnectorServers, and
also the reason that we are careful to discard the
ArrayNotificationBuffer (and its BufferListeners) when there are no
longer any ConnectorServers using it.
The synchronization of this class is inherently complex. In an attempt
to limit the complexity, we use just two locks:
- globalLock controls access to the mapping between an MBeanServer
and its ArrayNotificationBuffer and to the set of ShareBuffers for
each ArrayNotificationBuffer.
- the instance lock of each ArrayNotificationBuffer controls access
to the array of notifications, including its size, and to the
mechanism is used to indicate changes to the array.
If both locks are held at the same time, the globalLock must be
taken first.
Since adding or removing a BufferListener to an MBean can involve
calling user code, we are careful not to hold any locks while it is
done.
*/
private boolean disposed = false;
// FACTORY STUFF, INCLUDING SHARING
private static final
//Find out queue size
boolean create;
synchronized (globalLock) {
if (create) {
}
}
/* We avoid holding any locks while calling createListeners.
* This prevents possible deadlocks involving user code, but
* does mean that a second ConnectorServer created and started
* in this window will return before all the listeners are ready,
* which could lead to surprising behaviour. The alternative
* would be to block the second ConnectorServer until the first
* one has finished adding all the listeners, but that would then
* be subject to deadlock.
*/
if (create)
return sharer;
}
/* Ensure that this buffer is no longer the one that will be returned by
* getNotificationBuffer. This method is idempotent - calling it more
* than once has no effect beyond that of calling it once.
*/
synchronized (globalLock) {
}
}
synchronized (globalLock) {
synchronized (this) {
}
}
}
boolean empty;
synchronized (globalLock) {
if (empty)
else {
int max = 0;
}
}
}
if (empty) {
synchronized (this) {
disposed = true;
// Notify potential waiting fetchNotification call
notifyAll();
}
}
}
return;
}
addSharer(this);
}
public NotificationResult
long startSequenceNumber,
long timeout,
int maxNotifications)
throws InterruptedException {
}
public void dispose() {
ArrayNotificationBuffer.this.removeSharer(this);
}
int getSize() {
return size;
}
private final int size;
}
// ARRAYNOTIFICATIONBUFFER IMPLEMENTATION
throw new IllegalArgumentException("Bad args");
this.mBeanServer = mbs;
this.nextSequenceNumber = this.earliestSequenceNumber;
}
private synchronized boolean isDisposed() {
return disposed;
}
// We no longer support calling this method from outside.
// The JDK doesn't contain any such calls and users are not
// supposed to be accessing this class.
public void dispose() {
throw new UnsupportedOperationException();
}
/**
* <p>Fetch notifications that match the given listeners.</p>
*
* <p>The operation only considers notifications with a sequence
* number at least <code>startSequenceNumber</code>. It will take
* no longer than <code>timeout</code>, and will return no more
* than <code>maxNotifications</code> different notifications.</p>
*
* <p>If there are no notifications matching the criteria, the
* operation will block until one arrives, subject to the
* timeout.</p>
*
* @param filter an object that will add notifications to a
* {@code List<TargetedNotification>} if they match the current
* listeners with their filters.
* @param startSequenceNumber the first sequence number to
* consider.
* @param timeout the maximum time to wait. May be 0 to indicate
* not to wait if there are no notifications.
* @param maxNotifications the maximum number of notifications to
* return. May be 0 to indicate a wait for eligible notifications
* that will return a usable <code>nextSequenceNumber</code>. The
* {@link TargetedNotification} array in the returned {@link
* NotificationResult} may contain more than this number of
* elements but will not contain more than this number of
* different notifications.
*/
public NotificationResult
long startSequenceNumber,
long timeout,
int maxNotifications)
throws InterruptedException {
synchronized(this) {
return new NotificationResult(earliestSequenceNumber(),
new TargetedNotification[0]);
}
}
// Check arg validity
|| maxNotifications < 0) {
throw new IllegalArgumentException("Bad args to fetch");
}
"; max=" + maxNotifications);
}
if (startSequenceNumber > nextSequenceNumber()) {
throw new IllegalArgumentException(msg);
}
/* Determine the end time corresponding to the timeout value.
Caller may legitimately supply Long.MAX_VALUE to indicate no
timeout. In that case the addition will overflow and produce
a negative end time. Set end time to Long.MAX_VALUE in that
case. We assume System.currentTimeMillis() is positive. */
/* We set earliestSeq the first time through the loop. If we
set it here, notifications could be dropped before we
started examining them, so earliestSeq might not correspond
to the earliest notification we examined. */
long earliestSeq = -1;
long nextSeq = startSequenceNumber;
new ArrayList<TargetedNotification>();
/* On exit from this loop, notifs, earliestSeq, and nextSeq must
all be correct values for the returned NotificationResult. */
while (true) {
/* Get the next available notification regardless of filters,
or wait for one to arrive if there is none. */
synchronized (this) {
/* First time through. The current earliestSequenceNumber
is the first one we could have examined. */
if (earliestSeq < 0) {
"earliestSeq=" + earliestSeq);
}
if (nextSeq < earliestSeq) {
"nextSeq=earliestSeq");
}
} else
/* If many notifications have been dropped since the
last time through, nextSeq could now be earlier
than the current earliest. If so, notifications
may have been lost and we return now so the caller
can see this next time it calls. */
if (nextSeq < earliestSeq) {
earliestSeq + " so may have lost notifs");
break;
}
if (nextSeq < nextSequenceNumber()) {
// Skip security check if NotificationBufferFilter is not overloaded
try {
} catch (InstanceNotFoundException | SecurityException e) {
}
++nextSeq;
continue;
}
}
nextSeq);
}
} else {
/* nextSeq is the largest sequence number. If we
already got notifications, return them now.
Otherwise wait for some to arrive, with
timeout. */
"no more notifs but have some so don't wait");
break;
}
if (toWait <= 0) {
break;
}
/* dispose called */
if (isDisposed()) {
"dispose callled, no wait");
return new NotificationResult(earliestSequenceNumber(),
new TargetedNotification[0]);
}
continue;
}
}
/* We have a candidate notification. See if it matches
our filters. We do this outside the synchronized block
so we don't hold up everyone accessing the buffer
(including notification senders) while we evaluate
potentially slow filters. */
new ArrayList<TargetedNotification>();
"applying filter to candidate");
/* We only check the max size now, so that our
returned nextSeq is as large as possible. This
prevents the caller from thinking it missed
interesting notifications when in fact we knew they
weren't. */
if (maxNotifications <= 0) {
"reached maxNotifications");
break;
}
}
++nextSeq;
} // end while
/* Construct and return the result. */
new TargetedNotification[nnotifs];
return nr;
}
synchronized long earliestSequenceNumber() {
return earliestSequenceNumber;
}
synchronized long nextSequenceNumber() {
return nextSequenceNumber;
}
"dropped oldest notif, earliestSeq=" +
}
}
notifyAll();
}
private void dropNotification() {
}
+ earliestSequenceNumber + ")";
throw new IllegalArgumentException(msg);
}
}
private static class NamedNotification {
this.notification = notif;
}
return sender;
}
return notification;
}
}
}
/*
* Add our listener to every NotificationBroadcaster MBean
* currently in the MBean server and to every
* NotificationBroadcaster later created.
*
* It would be really nice if we could just do
* mbs.addNotificationListener(new ObjectName("*:*"), ...);
* Definitely something for the next version of JMX.
*
* There is a nasty race condition that we must handle. We
* first register for MBean-creation notifications so we can add
* listeners to new MBeans, then we query the existing MBeans to
* add listeners to them. The problem is that a new MBean could
* arrive after we register for creations but before the query has
* completed. Then we could see the MBean both in the query and
* in an MBean-creation notification, and we would end up
* registering our listener twice.
*
* To solve this problem, we arrange for new MBeans that arrive
* while the query is being done to be added to the Set createdDuringQuery
* and we do not add a listener immediately. When the query is done,
* we atomically turn off the addition of new names to createdDuringQuery
* and add all the names that were there to the result of the query.
* Since we are dealing with Sets, the result is the same whether or not
* the newly-created MBean was included in the query result.
*
* It is important not to hold any locks during the operation of adding
* listeners to MBeans. An MBean's addNotificationListener can be
* arbitrary user code, and this could deadlock with any locks we hold
* (see bug 6239400). The corollary is that we must not do any operations
* in this method or the methods it calls that require locks.
*/
private void createListeners() {
synchronized (this) {
}
try {
} catch (Exception e) {
throw re;
}
/* Spec doesn't say whether Set returned by QueryNames can be modified
so we clone it. */
synchronized (this) {
}
}
checkNoLocks();
try {
} catch (Exception e) {
/* This can happen if the MBean was unregistered just
after the query. Or user NotificationBroadcaster might
throw unexpected exception. */
}
}
checkNoLocks();
try {
} catch (Exception e) {
}
}
final NotificationListener listener,
final NotificationFilter filter,
throws Exception {
try {
handback);
return null;
}
});
} catch (Exception e) {
throw extractException(e);
}
}
final NotificationListener listener)
throws Exception {
try {
return null;
}
});
} catch (Exception e) {
throw extractException(e);
}
}
}
};
try {
} catch (RuntimeException e) {
throw e;
}
}
final ObjectName name,
new PrivilegedExceptionAction<Boolean>() {
}
};
try {
} catch (Exception e) {
return false;
}
}
/* This method must not be synchronized. See the comment on the
* createListeners method.
*
* The notification could arrive after our buffer has been destroyed
* or even during its destruction. So we always add our listener
* (without synchronization), then we check if the buffer has been
* destroyed and if so remove the listener we just added.
*/
final String shouldEqual =
return;
}
synchronized (this) {
if (createdDuringQuery != null) {
return;
}
}
if (isDisposed())
}
}
}
}
}
private static class BroadcasterQuery
}
}
static {
}
new NotificationListener() {
}
};
private void destroyListeners() {
checkNoLocks();
try {
} catch (Exception e) {
}
"remove listener from " + name);
}
}
private void checkNoLocks() {
}
/**
* Iterate until we extract the real exception
* from a stack of PrivilegedActionExceptions.
*/
while (e instanceof PrivilegedActionException) {
e = ((PrivilegedActionException)e).getException();
}
return e;
}
new ClassLogger("javax.management.remote.misc",
"ArrayNotificationBuffer");
private int queueSize;
private long earliestSequenceNumber;
private long nextSequenceNumber;
NotificationBroadcaster.class.getName();
}