You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@river.apache.org by pe...@apache.org on 2011/06/19 02:43:42 UTC
svn commit: r1137269 [2/2] - in
/river/jtsk/trunk/src/com/sun/jini/jeri/internal/runtime:
BasicExportTable.java Binding.java DgcRequestDispatcher.java
ImplRefManager.java JvmLifeSupport.java Lease.java ObjectTable.java
SequenceEntry.java Target.java
Added: river/jtsk/trunk/src/com/sun/jini/jeri/internal/runtime/Target.java
URL: http://svn.apache.org/viewvc/river/jtsk/trunk/src/com/sun/jini/jeri/internal/runtime/Target.java?rev=1137269&view=auto
==============================================================================
--- river/jtsk/trunk/src/com/sun/jini/jeri/internal/runtime/Target.java (added)
+++ river/jtsk/trunk/src/com/sun/jini/jeri/internal/runtime/Target.java Sun Jun 19 00:43:42 2011
@@ -0,0 +1,496 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.sun.jini.jeri.internal.runtime;
+
+import com.sun.jini.jeri.internal.runtime.ImplRefManager.ImplRef;
+import com.sun.jini.jeri.internal.runtime.ObjectTable.NoSuchObject;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.rmi.Remote;
+import java.rmi.server.ExportException;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import net.jini.export.ServerContext;
+import net.jini.id.Uuid;
+import net.jini.jeri.InboundRequest;
+import net.jini.jeri.InvocationDispatcher;
+import net.jini.security.SecurityContext;
+
+/**
+ * A Target represents a remote object, exported by BasicJeriExporter.
+ *
+ * Based on original ObjectTable.Target, modified to support forced interrupted
+ * unexport.
+ *
+ * @since 2.2.0
+ * @author Peter Firmstone
+ */
+final class Target {
+ private static final Logger logger =
+ Logger.getLogger("net.jini.jeri.BasicJeriExporter");
+
+ private volatile ImplRef implRef;
+ private final Uuid id;
+ private final DgcRequestDispatcher[] requestDispatchers;
+ private final boolean allowDGC;
+ private final boolean keepAlive;
+ private final SecurityContext securityContext;
+ private final ClassLoader ccl;
+ private final Lock lock = new ReentrantLock();
+ private volatile InvocationDispatcher invocationDispatcher;
+ private volatile boolean exported = false;
+ private volatile boolean unexported = false;
+ private volatile boolean success = false;
+ private volatile boolean interrupted = false;
+ private final Set<Uuid> referencedSet;
+ private final Map<Uuid, SequenceEntry> sequenceTable;
+ private final JvmLifeSupport keepAliveCount;
+ private volatile boolean decrementedKeepAlive = false;
+ private final ObjectTable objTable;
+ private final Collection<Thread> calls = new ArrayList<Thread>();
+
+ /**
+ * Construction must be directly followed by three calls.
+ *
+ * procRequestDispatchers()
+ * setImplRef(ImplRef implRef)
+ * export()
+ *
+ * @param id
+ * @param requestDispatchers
+ * @param allowDGC
+ * @param keepAlive
+ * @param table
+ * @param sc
+ * @param contextCl
+ * @param counter
+ * @throws java.rmi.server.ExportException
+ */
+ Target(Uuid id, DgcRequestDispatcher[] requestDispatchers, boolean allowDGC, boolean keepAlive,
+ ObjectTable table, SecurityContext sc, ClassLoader contextCl,
+ JvmLifeSupport counter) throws ExportException {
+ super();
+ this.objTable = table;
+ this.id = id;
+ this.requestDispatchers = requestDispatchers;
+ this.allowDGC = allowDGC;
+ this.keepAlive = keepAlive;
+ this.keepAliveCount = counter;
+
+ securityContext = sc;
+ ccl = contextCl;
+ if (allowDGC) {
+ referencedSet = new HashSet<Uuid>(3);
+ sequenceTable = new HashMap<Uuid, SequenceEntry>(3);
+ } else {
+ referencedSet = null;
+ sequenceTable = null;
+ }
+ }
+
+ /**
+ * Must be synchronized externally by the object table.
+ * Synchronization cannot be performed by a class lock, there may
+ * be more than one object table.
+ *
+ * Unsynchronized method.
+ */
+ void procRequestDispatchers() throws ExportException{
+ if (exported) throw new ExportException("Target already exported");
+ int i = 0;
+ try {
+ for (i = 0; i < requestDispatchers.length; i++) {
+ requestDispatchers[i].put(this);
+ }
+ success = true;
+ } finally {
+ if (!success) {
+ for (int j = 0; j < i; j++) {
+ requestDispatchers[i].remove(this, false);
+ }
+ }
+ }
+ }
+
+ /**
+ * Set the ImplRef.
+ *
+ * Unsynchronized method, with volatile internal visibility, set
+ * after construction, prior to setExport.
+ *
+ * @param implRef
+ */
+ void setImplRef(ImplRef implRef) throws ExportException{
+ if (exported) throw new ExportException("Target already exported");
+ this.implRef = implRef;
+ }
+
+ void setInvocationDispatcher(InvocationDispatcher id) {
+ assert id != null;
+ lock.lock();
+ try {
+ assert invocationDispatcher == null;
+ invocationDispatcher = id;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+
+ /**
+ * This method is called after construction, processing RequestDispatchers,
+ * creating and setting an ImplRef.
+ *
+ * It should not be called if the object has been unexported.
+ *
+ * Unsynchronized method.
+ */
+ void setExported() throws ExportException{
+ if (exported) throw new ExportException("Target already exported");
+ if (unexported == true) throw new ExportException("Target cannot be re-exported");
+ if (implRef == null) throw new ExportException("ImplRef cannot be null");
+ if (success == false) throw new ExportException("RequestDispatchers unsuccessful");
+ exported = true;
+ if (keepAlive){
+ keepAliveCount.incrementKeepAliveCount();
+ }
+ }
+
+ private void decrementKeepAliveCount(){
+ if (keepAlive){
+ if (decrementedKeepAlive) return; // Ensure only once per target.
+ decrementedKeepAlive = true;
+ keepAliveCount.decrementKeepAliveCount();
+ }
+ }
+
+ /**
+ * To quote the Exporter interface:
+ *
+ * If the remote object is unexported as a result of this method,
+ * then the implementation may (and should, if possible) prevent
+ * remote calls in progress from being able to communicate their
+ * results successfully.
+ *
+ * To comply with the above, dispatch call interruption has been added.
+ *
+ * @param force - if true forcibly unexported
+ * @return true - if unexport successful
+ */
+ boolean unexport(boolean force) {
+ if (!exported) return true;
+ lock.lock();
+ try {
+ if (!force && !calls.isEmpty()) {
+ return false;
+ }
+ unexported = true;
+ exported = false;
+ if ( force && !calls.isEmpty()){
+ interrupted = true;
+ Iterator<Thread> i = calls.iterator();
+ while (i.hasNext()){
+ i.next().interrupt();
+ i.remove();
+ }
+ }
+ if (calls.isEmpty()) {
+ decrementKeepAliveCount();
+ }
+ if (allowDGC) {
+ if (!referencedSet.isEmpty()) {
+ for (Iterator i = referencedSet.iterator(); i.hasNext();) {
+ Uuid clientID = (Uuid) i.next();
+ objTable.unregisterTarget(this, clientID);
+ }
+ referencedSet.clear();
+ }
+ sequenceTable.clear();
+ }
+ } finally {
+ lock.unlock();
+ }
+ implRef.release(this);
+ for (int i = 0; i < requestDispatchers.length; i++) {
+ requestDispatchers[i].remove(this, false);
+ }
+ return true;
+ }
+
+ void collect() {
+ if (!exported) return;
+ lock.lock();
+ try {
+ if (logger.isLoggable(Level.FINE)) {
+ logger.log(Level.FINE, "garbage collection of object with id {0}", id);
+ }
+ unexported = true;
+ exported = false;
+
+ if (calls.isEmpty()) {
+ decrementKeepAliveCount();
+ }
+
+ if (allowDGC) {
+ assert referencedSet.isEmpty();
+ sequenceTable.clear();
+ }
+ } finally {
+ lock.unlock();
+ }
+
+ for (int i = 0; i < requestDispatchers.length; i++) {
+ requestDispatchers[i].remove(this, true);
+ }
+ }
+
+ Uuid getObjectIdentifier() {
+ return id;
+ }
+
+ // used by ImplRef for invoking Unreferenced.unreferenced
+ boolean getEnableDGC() {
+ return allowDGC;
+ }
+
+ SecurityContext getSecurityContext() {
+ return securityContext;
+ }
+
+ ClassLoader getContextClassLoader() {
+ return ccl;
+ }
+
+ void referenced(Uuid clientID, long sequenceNum) {
+ if (!allowDGC) return;
+ if (!exported) return;
+ lock.lock();
+ try {
+ if (logger.isLoggable(Level.FINEST)) {
+ logger.log(Level.FINEST, "this={0}, clientID={1}, sequenceNum={2}", new Object[]{this, clientID, new Long(sequenceNum)});
+ }
+ SequenceEntry entry = (SequenceEntry) sequenceTable.get(clientID);
+ if (entry == null) {
+ entry = new SequenceEntry(sequenceNum);
+ sequenceTable.put(clientID, entry);
+ } else if (!entry.update(sequenceNum, false)) {
+ /* entry will be updated if sequenceNum is greater
+ * return if entry was not updated.
+ */
+ return;
+ }
+ if (!referencedSet.contains(clientID)) {
+ if (referencedSet.isEmpty()) {
+ Remote impl = implRef.getImpl();
+ if (impl == null) {
+ return;
+ }
+ implRef.pin(this);
+ }
+ referencedSet.add(clientID);
+
+ objTable.registerTarget(this, clientID);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ void unreferenced(Uuid clientID, long sequenceNum, boolean strong) {
+ if (!allowDGC) return;
+ if (!exported) return;
+ lock.lock();
+ try {
+ if (logger.isLoggable(Level.FINEST)) {
+ logger.log(Level.FINEST, "this={0}, clientID={1}, sequenceNum={2}, strong={3}", new Object[]{this, clientID, new Long(sequenceNum), Boolean.valueOf(strong)});
+ }
+ SequenceEntry entry = sequenceTable.get(clientID);
+ if (entry == null) {
+ if (strong) {
+ entry = new SequenceEntry(sequenceNum, strong);
+ sequenceTable.put(clientID, entry);
+ }
+ } else if (!entry.update(sequenceNum, strong)) {
+ return;
+ } else if (!entry.keep()) {
+ sequenceTable.remove(clientID);
+ }
+
+ objTable.unregisterTarget(this, clientID);
+
+ if (referencedSet.remove(clientID) && referencedSet.isEmpty()) {
+ implRef.unpin(this);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ void leaseExpired(Uuid clientID) {
+ assert allowDGC;
+ if (!exported) return;
+ lock.lock();
+ try {
+ if (logger.isLoggable(Level.FINEST)) {
+ logger.log(Level.FINEST, "this={0}, clientID={1}", new Object[]{this, clientID});
+ }
+ SequenceEntry entry = sequenceTable.get(clientID);
+ if (entry != null && !entry.keep()) {
+ /*
+ * REMIND: We could be removing the sequence number
+ * for a more recent lease, thus allowing a "late
+ * clean call" to be inappropriately processed?
+ * (See 4848840 Comments.) See River-142
+ *
+ * FIXED see ObjectTable
+ */
+ sequenceTable.remove(clientID);
+ }
+
+ if (referencedSet.remove(clientID) && referencedSet.isEmpty()) {
+ implRef.unpin(this);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private void interrupted(Thread currentThread) throws InterruptedException {
+ if (currentThread.interrupted()) { // clears the interrupt status.
+ throw new InterruptedException("Target interrupted during dispatch, unexported: " + unexported);
+ }
+ }
+
+ void dispatch(InboundRequest request) throws IOException, NoSuchObject {
+ if (!exported){ // optimisation to avoid locking.
+ if (logger.isLoggable(Level.FINEST)) {
+ logger.log(Level.FINEST, "this={0}, not exported", this);
+ }
+ throw new NoSuchObject();
+ }
+ Thread current = Thread.currentThread();
+ boolean exitNormally = true;
+ boolean callerAdded = false;
+ try {
+ InvocationDispatcher id = null;
+ lock.lockInterruptibly();
+ try {
+ callerAdded = calls.add(current);
+ if (!exported || invocationDispatcher == null) { // check again now we've got the lock.
+ if (logger.isLoggable(Level.FINEST)) {
+ logger.log(Level.FINEST, "this={0}, not exported", this);
+ }
+ throw new NoSuchObject();
+ }
+ id = invocationDispatcher;
+ } finally {
+ lock.unlock();
+ }
+ Remote impl = implRef.getImpl();
+ if (impl == null) {
+ if (logger.isLoggable(Level.FINEST)) {
+ logger.log(Level.FINEST, "this={0}, garbage collected", this);
+ }
+ throw new NoSuchObject();
+ }
+ interrupted(current);
+ dispatch(request, id, impl, current);
+ interrupted(current);
+ } catch (InterruptedException ex) {
+ exitNormally = false;
+ request.abort();
+ if (!interrupted){
+ // Not interrupted by unexport, reset interrupted status.
+ current.interrupt();
+ }// else interrupt is swallowed.
+ if (logger.isLoggable(Level.FINEST)) {
+ logger.log(Level.FINEST, "this={0}, interrupted" , this);
+ }
+ }finally {
+ // Either exit normally with clean up, or clean up if caller was added and unexport didn't interrupt.
+ if ( exitNormally || (callerAdded && !interrupted)) {
+ lock.lock();
+ try {
+ calls.remove(current);
+ if (!exported && calls.isEmpty()) { // In case Target was collected while call in progress.
+ decrementKeepAliveCount();
+ }
+ }finally {
+ lock.unlock();
+ }
+ } // else exit without cleanup.
+ }
+ }
+
+ private void dispatch(final InboundRequest request, final InvocationDispatcher id,
+ final Remote impl, Thread t) throws IOException, NoSuchObject {
+ ClassLoader savedCcl = t.getContextClassLoader();
+ try {
+ if (ccl != savedCcl) {
+ t.setContextClassLoader(ccl);
+ }
+ AccessController.doPrivileged(securityContext.wrap(new PrivilegedExceptionAction() {
+
+ public Object run() throws IOException, InterruptedException {
+ dispatch(request, id, impl);
+ return null;
+ }
+ }), securityContext.getAccessControlContext());
+ } catch (PrivilegedActionException e) {
+ throw (IOException) e.getException();
+ } finally {
+ if (ccl != savedCcl || savedCcl != t.getContextClassLoader()) {
+ t.setContextClassLoader(savedCcl);
+ }
+ }
+ }
+
+ private void dispatch(final InboundRequest request, final InvocationDispatcher id, final Remote impl) throws IOException, InterruptedException {
+ request.checkPermissions();
+ interrupted(Thread.currentThread());
+ OutputStream out = request.getResponseOutputStream();
+ out.write(Jeri.OBJECT_HERE);
+ final Collection context = new ArrayList(5);
+ request.populateContext(context);
+ ServerContext.doWithServerContext(new Runnable() {
+
+ public void run() {
+ id.dispatch(impl, request, context);
+ }
+ }, Collections.unmodifiableCollection(context));
+ }
+
+ public String toString() {
+ // for logging
+ return "Target@" + Integer.toHexString(hashCode()) + "[" + id + "]";
+ }
+}
Propchange: river/jtsk/trunk/src/com/sun/jini/jeri/internal/runtime/Target.java
------------------------------------------------------------------------------
svn:eol-style = native