diff src/main/gov/nasa/jpf/vm/MultiProcessVM.java @ 0:61d41facf527

initial v8 import (history reset)
author Peter Mehlitz <Peter.C.Mehlitz@nasa.gov>
date Fri, 23 Jan 2015 10:14:01 -0800
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/gov/nasa/jpf/vm/MultiProcessVM.java	Fri Jan 23 10:14:01 2015 -0800
@@ -0,0 +1,466 @@
+/*
+ * Copyright (C) 2014, United States Government, as represented by the
+ * Administrator of the National Aeronautics and Space Administration.
+ * All rights reserved.
+ *
+ * The Java Pathfinder core (jpf-core) platform is licensed 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 gov.nasa.jpf.vm;
+
+
+import gov.nasa.jpf.Config;
+import gov.nasa.jpf.JPF;
+import gov.nasa.jpf.JPFConfigException;
+import gov.nasa.jpf.util.IntTable;
+import gov.nasa.jpf.util.Misc;
+import gov.nasa.jpf.util.Predicate;
+import gov.nasa.jpf.vm.choice.BreakGenerator;
+
+import gov.nasa.jpf.vm.choice.ThreadChoiceFromSet;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A VM implementation that simulates running multiple applications within the same
+ * JPF process (centralized model checking of distributed applications).
+ * This is achieved by executing each application in a separate thread group,
+ * using separate SystemClassLoader instances to ensure proper separation of types / static fields.
+ * 
+ * 
+ * @author Nastaran Shafiei <nastaran.shafiei@gmail.com>
+ * 
+ * To use this jpf.properties includes,
+ *              vm.class = gov.nasa.jpf.vm.MultiProcessVM 
+ */
+public class MultiProcessVM extends VM {
+
+  static final int MAX_APP = 32;
+
+  ApplicationContext[] appCtxs;
+  
+  MultiProcessPredicate runnablePredicate;
+  MultiProcessPredicate appTimedoutRunnablePredicate;
+  MultiProcessPredicate appDaemonRunnablePredicate;
+  MultiProcessPredicate appPredicate;
+  protected Predicate<ThreadInfo> systemInUsePredicate;
+  
+  public MultiProcessVM (JPF jpf, Config conf) {
+    super(jpf, conf);
+    
+    appCtxs = createApplicationContexts();
+    
+    initializePredicates();
+  }
+  
+  void initializePredicates() {
+    runnablePredicate = new MultiProcessPredicate() {
+      @Override
+	public boolean isTrue (ThreadInfo t){
+        return (t.isRunnable() && this.appCtx == t.appCtx);
+      }
+    };
+    
+    appTimedoutRunnablePredicate = new MultiProcessPredicate() {
+      @Override
+	public boolean isTrue (ThreadInfo t){
+        return (this.appCtx == t.appCtx && t.isTimeoutRunnable());
+      }
+    }; 
+    
+    appDaemonRunnablePredicate = new MultiProcessPredicate() {
+      @Override
+	public boolean isTrue (ThreadInfo t){
+        return (this.appCtx == t.appCtx && t.isRunnable() && t.isDaemon());
+      }
+    };
+    
+    appPredicate = new MultiProcessPredicate() {
+      @Override
+	public boolean isTrue (ThreadInfo t){
+        return (this.appCtx == t.appCtx);
+      }
+    };
+    
+    
+    // this predicates collects those finalizers which are either runnable or
+    // have some queued objects to process.
+    systemInUsePredicate = new Predicate<ThreadInfo> () {
+      @Override
+	public boolean isTrue (ThreadInfo t){
+        boolean isTrue = false;
+        if(t.isSystemThread()) {
+          if(t.isRunnable()) {
+            isTrue = true;
+          } else {
+            FinalizerThreadInfo finalizer = (FinalizerThreadInfo) t;
+            isTrue = !finalizer.isIdle();
+          }
+        }
+        return isTrue;
+      }
+    };
+  }
+
+  /**
+   * <2do> this should also handle command line specs such as "jpf ... tgt1 tgt1_arg ... -- tgt2 tgt2_arg ... 
+   */
+  ApplicationContext[] createApplicationContexts(){
+    String[] targets;
+
+    int replicate = config.getInt("target.replicate", 0);
+    if(replicate>0) {
+      String target = config.getProperty("target");
+      targets = new String[replicate];
+      for(int i=0; i<replicate; i++) {
+        targets[i] = target;
+      }
+    } else {
+      targets = config.getStringEnumeration("target", MAX_APP);
+    }
+
+    if (targets == null){
+      throw new JPFConfigException("no applications specified, check 'target.N' settings");
+    }
+    
+    ArrayList<ApplicationContext> list = new ArrayList<ApplicationContext>(targets.length);
+    for (int i=0; i<targets.length; i++){
+      if (targets[i] != null){ // there might be holes in the array
+        String clsName = targets[i];
+        if (!isValidClassName(clsName)) {
+          throw new JPFConfigException("main class not a valid class name: " + clsName);
+        }
+        
+        String argsKey;
+        String entryKey;
+        String hostKey;
+        if(replicate>0) {
+          argsKey = "target.args";
+          entryKey = "target.entry";
+          hostKey = "target.host";
+        } else {
+          argsKey = "target.args." + i;
+          entryKey = "target.entry." + i;
+          hostKey = "target.host." + i;
+        }
+        
+        String[] args = config.getCompactStringArray(argsKey);
+        if (args == null){
+          args = EMPTY_ARGS;
+        }
+        
+        String mainEntry = config.getString(entryKey, "main([Ljava/lang/String;)V");
+        
+        String host = config.getString(hostKey, "localhost");
+        
+        SystemClassLoaderInfo sysCli = createSystemClassLoaderInfo(list.size());
+    
+        ApplicationContext appCtx = new ApplicationContext( i, clsName, mainEntry, args, host, sysCli);
+        list.add( appCtx);
+      }
+    }
+    
+    return list.toArray(new ApplicationContext[list.size()]);
+  }
+
+  @Override
+  public boolean initialize(){
+    try {
+      ThreadInfo tiFirst = null;
+      
+      for (int i=0; i<appCtxs.length; i++){
+        ApplicationContext appCtx = appCtxs[i];
+    
+        // this has to happen before we load the startup classes during initializeMainThread
+        scheduler.initialize(this, appCtx);
+    
+        ThreadInfo tiMain = initializeMainThread(appCtx, i);
+        initializeFinalizerThread(appCtx, appCtxs.length+i);
+        
+        if (tiMain == null) {
+          return false; // bail out
+        }
+        if (tiFirst == null){
+          tiFirst = tiMain;
+        }
+      }
+
+      initSystemState(tiFirst);
+      initialized = true;
+      notifyVMInitialized();
+      
+      return true;
+      
+    } catch (JPFConfigException cfe){
+      log.severe(cfe.getMessage());
+      return false;
+    } catch (ClassInfoException cie){
+      log.severe(cie.getMessage());
+      return false;
+    }
+    // all other exceptions are JPF errors that should cause stack traces
+  }
+
+  @Override
+  public int getNumberOfApplications(){
+    return appCtxs.length;
+  }
+    
+  @Override
+  public ApplicationContext getApplicationContext(int objRef) {
+    VM vm = VM.getVM();
+
+    ClassInfo ci = vm.getElementInfo(objRef).getClassInfo();
+    while(!ci.isObjectClassInfo()) {
+      ci = ci.getSuperClass();
+    }
+
+    ClassLoaderInfo sysLoader = ci.getClassLoaderInfo();
+    ApplicationContext[] appContext = vm.getApplicationContexts();
+    
+    for(int i=0; i<appContext.length; i++) {
+      if(appContext[i].getSystemClassLoader() == sysLoader) {
+        return appContext[i];
+      }
+    }
+    return null;
+  }
+  
+  @Override
+  public ApplicationContext[] getApplicationContexts(){
+    return appCtxs;
+  }
+
+  @Override
+  public ApplicationContext getCurrentApplicationContext(){
+    ThreadInfo ti = ThreadInfo.getCurrentThread();
+    if (ti != null){
+      return ti.getApplicationContext();
+    } else {
+      // return the last defined one
+      return appCtxs[appCtxs.length-1];
+    }
+  }
+
+  
+  @Override
+  public String getSUTName() {
+    StringBuilder sb = new StringBuilder();
+    
+    for (int i=0; i<appCtxs.length; i++){
+      if (i>0){
+        sb.append("+");
+      }
+      sb.append(appCtxs[i].mainClassName);
+    }
+    
+    return sb.toString();
+  }
+
+  @Override
+  public String getSUTDescription(){
+    StringBuilder sb = new StringBuilder();
+    
+    for (int i=0; i<appCtxs.length; i++){
+      if (i>0){
+        sb.append('+'); // "||" would be more fitting, but would screw up filenames
+      }
+
+      ApplicationContext appCtx = appCtxs[i];
+      sb.append(appCtx.mainClassName);
+      sb.append('.');
+      sb.append(Misc.upToFirst(appCtx.mainEntry, '('));
+
+      sb.append('(');
+      String[] args = appCtx.args;
+      for (int j = 0; j < args.length; j++) {
+        if (j > 0) {
+          sb.append(',');
+        }
+        sb.append('"');
+        sb.append(args[j]);
+        sb.append('"');
+      }
+      sb.append(')');
+    }
+    
+    return sb.toString();
+  }
+
+  
+  @Override
+  public boolean isSingleProcess() {
+    return false;
+  }
+
+  @Override
+  public boolean isEndState () {
+    boolean hasNonTerminatedDaemon = getThreadList().hasAnyMatching(getUserLiveNonDaemonPredicate());
+    boolean hasRunnable = getThreadList().hasAnyMatching(getUserTimedoutRunnablePredicate());
+    boolean isEndState = !(hasNonTerminatedDaemon && hasRunnable);
+    
+    if(processFinalizers) {
+      if(isEndState) {
+        int n = getThreadList().getMatchingCount(systemInUsePredicate);
+        if(n>0) {
+          return false;
+        }
+      }
+    }
+    
+    return isEndState;
+  }
+
+  @Override
+  // Note - for now we just check for global deadlocks not the local ones which occur within a
+  // scope of a single progress
+  public boolean isDeadlocked () { 
+    boolean hasNonDaemons = false;
+    boolean hasBlockedThreads = false;
+
+    if (ss.isBlockedInAtomicSection()) {
+      return true; // blocked in atomic section
+    }
+
+    ThreadInfo[] threads = getThreadList().getThreads();
+    int len = threads.length;
+
+    boolean hasUserThreads = false;
+    for (int i=0; i<len; i++){
+      ThreadInfo ti = threads[i];
+      
+      if (ti.isAlive()) {
+        hasNonDaemons |= !ti.isDaemon();
+
+        // shortcut - if there is at least one runnable, we are not deadlocked
+        if (ti.isTimeoutRunnable()) { // willBeRunnable() ?
+          return false;
+        }
+        
+        if(!ti.isSystemThread()) {
+          hasUserThreads = true;
+        }
+
+        // means it is not NEW or TERMINATED, i.e. live & blocked
+        hasBlockedThreads = true;
+      }
+    }
+
+    boolean isDeadlock = hasNonDaemons && hasBlockedThreads;
+    
+    if(processFinalizers && isDeadlock && !hasUserThreads) {
+      // all threads are blocked, system threads. If at least one of them 
+      // is in-use, then this is a deadlocked state.
+      return (getThreadList().getMatchingCount(systemInUsePredicate)>0);
+    }
+    
+    return isDeadlock;
+  }
+  
+  @Override
+  public void terminateProcess (ThreadInfo ti) {
+    SystemState ss = getSystemState();
+    ThreadInfo[] appThreads = getThreadList().getAllMatching(getAppPredicate(ti));
+    ThreadInfo finalizerTi = null;
+
+    for (int i = 0; i < appThreads.length; i++) {
+      ThreadInfo t = appThreads[i];
+      
+      // if finalizers have to be processed, FinalizerThread is not killed at this 
+      // point. We need to keep it around in case fianlizable objects are GCed after 
+      // System.exit() returns.
+      if(processFinalizers && t.isSystemThread()) {
+        finalizerTi = t;
+      } else {
+        // keep the stack frames around, so that we can inspect the snapshot
+        t.setTerminated();
+      }
+    }
+    
+    ThreadList tl = getThreadList();
+    
+    ChoiceGenerator<ThreadInfo> cg;
+    if (tl.hasAnyMatching(getAlivePredicate())) {
+      ThreadInfo[] runnables = getThreadList().getAllMatching(getTimedoutRunnablePredicate());
+      cg = new ThreadChoiceFromSet( "PROCESS_TERMINATE", runnables, true);
+      GlobalSchedulingPoint.setGlobal(cg);
+      
+    } else {
+      cg = new BreakGenerator("exit", ti, true);
+    }
+    
+    ss.setMandatoryNextChoiceGenerator(cg, "exit without break CG");
+    
+    // if there is a finalizer thread, we have to run the last GC, to queue finalizable objects, if any
+    if(finalizerTi != null) {
+      assert finalizerTi.isAlive();
+      activateGC();
+    }
+  }
+  
+  @Override
+  public Map<Integer,IntTable<String>> getInitialInternStringsMap() {
+    Map<Integer,IntTable<String>> interns = new HashMap<Integer,IntTable<String>>();
+     
+    for(ApplicationContext appCtx:getApplicationContexts()) {
+      interns.put(appCtx.getId(), appCtx.getInternStrings());
+    }
+    
+    return interns;
+  }
+  
+  //---------- Predicates used to query threads from ThreadList ----------//
+  
+  abstract class MultiProcessPredicate implements Predicate<ThreadInfo> {
+    ApplicationContext appCtx;
+
+    public void setAppCtx (ApplicationContext appCtx) { 
+      this.appCtx = appCtx; 
+    }
+  }
+  
+  @Override
+  public Predicate<ThreadInfo> getRunnablePredicate() {
+    runnablePredicate.setAppCtx(getCurrentApplicationContext());
+    return runnablePredicate;
+  }
+  
+  @Override
+  public Predicate<ThreadInfo> getAppTimedoutRunnablePredicate() {
+    appTimedoutRunnablePredicate.setAppCtx(getCurrentApplicationContext());
+    return appTimedoutRunnablePredicate;
+  }
+  
+  @Override
+  public Predicate<ThreadInfo> getDaemonRunnablePredicate() {
+    appDaemonRunnablePredicate.setAppCtx(getCurrentApplicationContext());
+    return appDaemonRunnablePredicate;
+  }
+  
+  /**
+   * Returns a predicate used to obtain all the threads that belong to the same application as ti
+   */
+  Predicate<ThreadInfo> getAppPredicate (final ThreadInfo ti){
+    appPredicate.setAppCtx(ti.getApplicationContext());
+    return appPredicate;
+  }
+  
+  // ---------- Methods for handling finalizers ---------- //
+
+  @Override
+  void updateFinalizerQueues () {
+    for(ApplicationContext appCtx: appCtxs) {
+      appCtx.getFinalizerThread().processNewFinalizables();
+    }
+  }
+}