/* * @(#)file SnmpSession.java * @(#)author Sun Microsystems, Inc. * @(#)version 1.12 * @(#)date 06/05/03 * * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * */ package com.sun.jmx.snmp.daemon; // java imports // import java.util.Vector; import java.util.Enumeration; import java.util.Hashtable; import java.util.Stack; import java.net.InetAddress; import java.net.SocketException; import java.io.InterruptedIOException; // jmx imports // import com.sun.jmx.snmp.SnmpDefinitions; import com.sun.jmx.snmp.SnmpStatusException; import com.sun.jmx.snmp.SnmpVarBindList; import com.sun.jmx.snmp.SnmpScopedPduRequest; // SNMP runtime imports // import com.sun.jmx.trace.Trace; /** * This class is used for sending INFORM REQUESTS from an agent to a manager. * * Creates, controls, and manages one or more inform requests. * * The SnmpSession maintains the list of all active inform requests and inform responses. * Each SnmpSession has a dispatcher that is a thread used to service all the inform requests it creates * and each SnmpSession uses a separate socket for sending/receiving inform requests/responses. * * An SnmpSession object is associated with an SNMP adaptor server. * It is created the first time an inform request is sent by the SNMP adaptor server * and is destroyed (with its associated SnmpSocket) when the SNMP adaptor server is stopped. * */ class SnmpSession implements SnmpDefinitions, Runnable { // PRIVATE VARIABLES //------------------ /** * The SNMP adaptor associated with this SnmpSession. */ protected transient SnmpAdaptorServer adaptor; /** * The SnmpSocket to be used to communicate with the manager * by all inform requests created in this session. */ protected transient SnmpSocket informSocket = null; /** * This table maintains the list of inform requests. */ private transient Hashtable informRequestList = new Hashtable(); /** * This table maintains the list of inform responses. * A FIFO queue is needed here. */ private transient Stack informRespq = new Stack(); /** * The dispatcher that will service all inform responses to inform requests generated * using this session object. An SnmpSession object creates one or more inform requests. * Thus it services all inform requests, which are created by this session object, * when an inform response arrives for an inform request generated by the session. */ private transient Thread myThread = null; /** * Request being synchronized from session thread. This happens when * a user does sync operation from a callback. */ private transient SnmpInformRequest syncInformReq ; SnmpQManager snmpQman = null; String dbgTag = "SnmpSession"; private boolean isBeingCancelled = false; // PUBLIC CONSTRUCTORS //-------------------- /** * Constructor for creating a new session. * @param adp The SNMP adaptor associated with this SnmpSession. * @exception SocketException Unable to initialize the SnmpSocket. */ public SnmpSession(SnmpAdaptorServer adp) throws SocketException { adaptor = adp; snmpQman = new SnmpQManager(); SnmpResponseHandler snmpRespHdlr = new SnmpResponseHandler(adp, snmpQman); initialize(adp, snmpRespHdlr); } /** * Constructor for creating a new session. Allows subclassing. */ public SnmpSession() throws SocketException { } // OTHER METHODS //-------------- /** * Initializes the SnmpSession. * @param adp The SNMP adaptor associated with this SnmpSession. * @exception SocketException Unable to initialize the SnmpSocket. */ protected synchronized void initialize(SnmpAdaptorServer adp, SnmpResponseHandler snmpRespHdlr) throws SocketException { informSocket = new SnmpSocket(snmpRespHdlr, adp.getAddress(), adp.getBufferSize().intValue()); myThread = new Thread(this, "SnmpSession"); myThread.start(); } /** * Indicates whether the thread for this session is active and the SNMP adaptor server ONLINE. * @return true if active, false otherwise. */ synchronized boolean isSessionActive() { //return ((myThread != null) && (myThread.isAlive())); return ((adaptor.isActive()) && (myThread != null) && (myThread.isAlive())); } /** * Gets the SnmpSocket which will be used by inform requests created in this session. * @return The socket which will be used in this session. */ SnmpSocket getSocket() { return informSocket; } /** * Gets the SnmpQManager which will be used by inform requests created in this session. * @return The SnmpQManager which will be used in this session. */ SnmpQManager getSnmpQManager() { return snmpQman; } /** * Indicates whether this session is performing synchronous operation for an inform request. * @return true if the session is performing synchronous operation, false otherwise. */ private synchronized boolean syncInProgress() { return syncInformReq != null ; } private synchronized void setSyncMode(SnmpInformRequest req) { syncInformReq = req ; } private synchronized void resetSyncMode() { if (syncInformReq == null) return ; syncInformReq = null ; if (thisSessionContext()) return ; this.notifyAll() ; } /** * Returns true if the current executing thread is this session's dispatcher. * Typically used to detect whether the user is doing a sync operation from * this dispatcher context. For instance, a user gives a sync command * from within a request callback using its associated session. * @return true if current thread is this session's dispatcher, false otherwise. */ boolean thisSessionContext() { return (Thread.currentThread() == myThread) ; } /** * Sends an inform request to the specified InetAddress destination using the specified community string. * @param addr The InetAddress destination for this inform request. * @param cs The community string to be used for the inform request. * @param cb The callback that is invoked when a request is complete. * @param vblst A list of SnmpVarBind instances or null. * @exception SnmpStatusException SNMP adaptor is not ONLINE or session * is dead. */ SnmpInformRequest makeAsyncRequest(InetAddress addr, String cs, SnmpInformHandler cb, SnmpVarBindList vblst, int port) throws SnmpStatusException { if (!isSessionActive()) { throw new SnmpStatusException("SNMP adaptor server not ONLINE"); } SnmpInformRequest snmpreq = new SnmpInformRequest(this, adaptor, addr, cs, port, cb); snmpreq.start(vblst); return snmpreq; } /** * Performs sync operations on active requests. Any number of inform requests * can be done in sync mode but only one per thread. * The user can do synchronous operation using the request handle only. */ void waitForResponse(SnmpInformRequest req, long waitTime) { if (! req.inProgress()) return ; setSyncMode(req) ; if (isTraceOn()) { trace("waitForResponse", "Session switching to sync mode for inform request " + req.getRequestId()); } long maxTime ; if (waitTime <= 0) maxTime = System.currentTimeMillis() + 6000 * 1000 ; else maxTime = System.currentTimeMillis() + waitTime ; while (req.inProgress() || syncInProgress()) { waitTime = maxTime - System.currentTimeMillis() ; if (waitTime <= 0) break ; synchronized (this) { if (! informRespq.removeElement(req)) { try { this.wait(waitTime) ; } catch(InterruptedException e) { } continue ; } } try { processResponse(req) ; } catch (Exception e) { if (isDebugOn()) { debug("waitForResponse", e); } } } resetSyncMode() ; return ; } /** * Dispatcher method for this session thread. This is the dispatcher method * which goes in an endless-loop and waits for servicing inform requests * which received a reply from the manager. */ public void run() { myThread = Thread.currentThread(); myThread.setPriority(Thread.NORM_PRIORITY); SnmpInformRequest reqc = null; while (myThread != null) { try { reqc = nextResponse(); if (reqc != null) { processResponse(reqc); } } catch (ThreadDeath d) { myThread = null; if (isDebugOn()) { debug("run", "Session thread unexpectedly shutting down"); } throw d ; } } if (isTraceOn()) { trace("run", "Session thread shutting down"); } myThread = null ; } private void processResponse(SnmpInformRequest reqc) { while (reqc != null && myThread != null) { try { if (reqc != null) { if (isTraceOn()) { trace("processResponse", "Processing response to req = " + reqc.getRequestId()); } reqc.processResponse() ; // Handles out of memory. reqc = null ; // finished processing. } } catch (Exception e) { if (isDebugOn()) { debug("processResponse", e); } reqc = null ; } catch (OutOfMemoryError ome) { if (isDebugOn()) { debug("processResponse", "Out of memory error in session thread"); debug("processResponse", ome); } Thread.currentThread().yield(); continue ; // re-process the request. } } } // HANDLING INFORM REQUESTS LIST AND INFORM RESPONSES LIST //-------------------------------------------------------- /** * Adds an inform request. * @param snmpreq The inform request to add. * @exception SnmpStatusException SNMP adaptor is not ONLINE or session is dead. */ synchronized void addInformRequest(SnmpInformRequest snmpreq) throws SnmpStatusException { // If the adaptor is not ONLINE, stop adding requests. // if (!isSessionActive()) { throw new SnmpStatusException("SNMP adaptor is not ONLINE or session is dead...") ; } informRequestList.put(snmpreq, snmpreq); } /** * Deletes an inform request. * @param snmpreq The inform request to delete. */ synchronized void removeInformRequest(SnmpInformRequest snmpreq) { // deleteRequest can be called from destroySnmpSession. //In such a case remove is done in cancelAllRequest method. if(!isBeingCancelled) informRequestList.remove(snmpreq) ; if (syncInformReq != null && syncInformReq == snmpreq) { resetSyncMode() ; } } /** * Cancels all pending inform requests in this session. */ private void cancelAllRequests() { final SnmpInformRequest[] list; synchronized(this) { if (informRequestList.isEmpty()) { return ; } isBeingCancelled = true; list = new SnmpInformRequest[informRequestList.size()]; java.util.Iterator it = informRequestList.values().iterator(); int i = 0; while(it.hasNext()) { SnmpInformRequest req = (SnmpInformRequest)it.next(); list[i++] = req; it.remove(); } informRequestList.clear(); } for(int i = 0; i < list.length; i++) list[i].cancelRequest(); } /** * Adds the inform request object which received a response to an inform request * generated by the session. This is added to a private store, which * will be eventually picked up by the dispatcher for processing. * @param reqc The inform request that received the response from the manager. */ void addResponse(SnmpInformRequest reqc) { SnmpInformRequest snmpreq = (SnmpInformRequest) reqc ; if (isSessionActive()) { synchronized(this) { informRespq.push(reqc) ; this.notifyAll() ; } } else { if (isDebugOn()) { debug("addResponse", "Adaptor not ONLINE or session thread dead. So inform response is dropped..." + reqc.getRequestId()); } } return ; } private synchronized SnmpInformRequest nextResponse() { if (informRespq.isEmpty()) { try { if (isTraceOn()) { trace("nextResponse", "Blocking for response"); } this.wait(); } catch(InterruptedException e) { } } if (informRespq.isEmpty()) return null; SnmpInformRequest reqc = (SnmpInformRequest) informRespq.firstElement(); informRespq.removeElementAt(0) ; return reqc ; } private synchronized void cancelAllResponses() { if (informRespq != null) { syncInformReq = null ; informRespq.removeAllElements() ; this.notifyAll() ; } } /** * Destroys any pending inform requests and then stops the session. * The session will not be usable after this method returns. */ final void destroySession() { cancelAllRequests() ; cancelAllResponses() ; synchronized(this) { informSocket.close() ; informSocket = null ; } snmpQman.stopQThreads() ; snmpQman = null ; killSessionThread() ; } /** * Make sure you are killing the thread when it is active. Instead * prepare for a graceful exit. */ private synchronized void killSessionThread() { if ((myThread != null) && (myThread.isAlive())) { if (isTraceOn()) { trace("killSessionThread", "Destroying session"); } if (!thisSessionContext()) { myThread = null ; this.notifyAll() ; } else myThread = null ; } } /** * Finalizer of the SnmpSession objects. * This method is called by the garbage collector on an object * when garbage collection determines that there are no more references to the object. *

Removes all the requests for this SNMP session, closes the socket and * sets all the references to the SnmpSession object to null. */ public void finalize() { if (informRespq != null) informRespq.removeAllElements() ; informRespq = null ; if (informSocket != null) informSocket.close() ; informSocket = null ; if (isTraceOn()) { trace("finalize", "Shutting all servers"); } snmpQman = null ; } // TRACES & DEBUG //--------------- boolean isTraceOn() { return Trace.isSelected(Trace.LEVEL_TRACE, Trace.INFO_ADAPTOR_SNMP); } void trace(String clz, String func, String info) { Trace.send(Trace.LEVEL_TRACE, Trace.INFO_ADAPTOR_SNMP, clz, func, info); } void trace(String func, String info) { trace(dbgTag, func, info); } boolean isDebugOn() { return Trace.isSelected(Trace.LEVEL_DEBUG, Trace.INFO_ADAPTOR_SNMP); } void debug(String clz, String func, String info) { Trace.send(Trace.LEVEL_DEBUG, Trace.INFO_ADAPTOR_SNMP, clz, func, info); } void debug(String clz, String func, Throwable exception) { Trace.send(Trace.LEVEL_DEBUG, Trace.INFO_ADAPTOR_SNMP, clz, func, exception); } void debug(String func, String info) { debug(dbgTag, func, info); } void debug(String func, Throwable exception) { debug(dbgTag, func, exception); } }