001package daikon.simplify; 002 003import static java.nio.charset.StandardCharsets.UTF_8; 004import static java.util.logging.Level.FINE; 005import static java.util.logging.Level.INFO; 006 007import java.io.BufferedReader; 008import java.io.Closeable; 009import java.io.IOException; 010import java.io.InputStream; 011import java.io.InputStreamReader; 012import java.util.logging.Logger; 013import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; 014import org.checkerframework.checker.lock.qual.GuardSatisfied; 015import org.checkerframework.checker.lock.qual.GuardedBy; 016import org.checkerframework.checker.mustcall.qual.MustCall; 017import org.checkerframework.checker.mustcall.qual.Owning; 018import org.checkerframework.checker.nullness.qual.MonotonicNonNull; 019import org.checkerframework.checker.nullness.qual.Nullable; 020import org.checkerframework.checker.nullness.qual.RequiresNonNull; 021 022/** A SessionManager is a component which handles the threading interaction with the Session. */ 023public class SessionManager implements Closeable { 024 /** The command to be performed (point of communication with worker thread). */ 025 private @Nullable Cmd pending; 026 027 /** Our worker thread; hold onto it so that we can stop it. */ 028 private @Owning Worker worker; 029 030 // The error message returned by the worked thread, or null 031 private @Nullable String error = null; 032 033 /** Debug tracer common to all Simplify classes. */ 034 public static final Logger debug = Logger.getLogger("daikon.simplify"); 035 036 // Deprecated method for setting the debug flag. 037 // // Enable to dump input and output to the console 038 // // Use "java -DDEBUG_SIMPLIFY=1 daikon.Daikon ..." or 039 // // "make USER_JAVA_FLAGS=-DDEBUG_SIMPLIFY=1 ..." 040 041 /** debugging flag */ 042 private static final boolean debug_mgr = debug.isLoggable(FINE); 043 044 /** 045 * log debug message 046 * 047 * @param s message to log 048 */ 049 public static void debugln(String s) { 050 if (!debug_mgr) { 051 return; 052 } 053 debug.fine(s); 054 } 055 056 public SessionManager() { 057 debugln("Creating SessionManager"); 058 worker = new Worker(); 059 worker.setDaemon(true); 060 debugln("Manager: starting worker"); 061 synchronized (this) { 062 worker.start(); 063 // We won't wake up from this until the worker thread is ready 064 // and wait()ing to accept requests. 065 try { 066 this.wait(); 067 } catch (InterruptedException e) { 068 // It's OK for a wait() to be interrupted. 069 } 070 } 071 debugln("SessionManager created"); 072 } 073 074 /** Performs the given command, or times out if too much time elapses. */ 075 public void request(Cmd command) throws TimeoutException { 076 assert worker != null : "Cannot use closed SessionManager"; 077 assert pending == null : "Cannot queue requests"; 078 if (debug.isLoggable(FINE)) { 079 System.err.println("Running command " + command); 080 System.err.println(" called from"); 081 Throwable t = new Throwable(); 082 t.printStackTrace(); 083 System.err.flush(); 084 } 085 synchronized (this) { 086 // place the command in the slot 087 assert pending == null; 088 pending = command; 089 // tell the worker to wake up 090 this.notifyAll(); 091 // wait for worker to finish 092 try { 093 this.wait(); 094 } catch (InterruptedException e) { 095 // It's OK for a wait() to be interrupted. 096 } 097 // command finished iff the command was nulled out 098 if (pending != null) { 099 close(); 100 throw new TimeoutException(); 101 } 102 // check for error 103 if (error != null) { 104 throw new SimplifyError(error); 105 } 106 } 107 } 108 109 /** Shutdown this session. No further commands may be executed. */ 110 @SuppressWarnings("nullness") // nulling worker for fast failure (& for GC) 111 @EnsuresCalledMethods(value = "worker", methods = "close") 112 @Override 113 public void close(@GuardSatisfied SessionManager this) { 114 worker.close(); 115 worker = null; 116 } 117 118 private static @MonotonicNonNull String prover_background = null; 119 120 private static String proverBackground() { 121 if (prover_background == null) { 122 try { 123 StringBuilder result = new StringBuilder(""); 124 String fileName; 125 if (daikon.inv.Invariant.dkconfig_simplify_define_predicates) { 126 fileName = "daikon-background-defined.txt"; 127 } else { 128 fileName = "daikon-background.txt"; 129 } 130 InputStream bg_stream = SessionManager.class.getResourceAsStream(fileName); 131 if (bg_stream == null) { 132 throw new RuntimeException( 133 "Could not find resource daikon/simplify/" + fileName + " on the classpath"); 134 } 135 BufferedReader lines = new BufferedReader(new InputStreamReader(bg_stream, UTF_8)); 136 String line; 137 while ((line = lines.readLine()) != null) { 138 line = line.trim(); 139 if ((line.length() == 0) || line.startsWith(";")) { 140 continue; 141 } 142 result.append(" "); 143 result.append(line); 144 result.append(daikon.Global.lineSep); 145 } 146 lines.close(); 147 prover_background = result.toString(); 148 } catch (IOException e) { 149 throw new RuntimeException("Could not load prover background"); 150 } 151 } 152 return prover_background; 153 } 154 155 public static int prover_instantiate_count = 0; 156 157 // Start up simplify, and send the universal backgound. 158 // Is successful exactly when return != null. 159 public static @Nullable SessionManager attemptProverStartup() { 160 SessionManager prover; 161 162 // Limit ourselves to a few tries 163 if (prover_instantiate_count > 5) { 164 return null; 165 } 166 167 // Start the prover 168 try { 169 prover_instantiate_count++; 170 prover = new SessionManager(); 171 } catch (SimplifyError e) { 172 System.err.println("Could not utilize Simplify: " + e); 173 return null; 174 } 175 176 try { 177 prover.request(new CmdRaw(proverBackground())); 178 } catch (TimeoutException e) { 179 throw new RuntimeException("Timeout on universal background", e); 180 } 181 return prover; 182 } 183 184 /** Helper thread which interacts with a Session, according to the enclosing manager. */ 185 @MustCall("close") private class Worker extends Thread implements Closeable { 186 /** The session mananger. */ 187 private final SessionManager mgr = SessionManager.this; // just sugar 188 189 /** The associated session, or null if the thread should shutdown. */ 190 private @Owning @Nullable @GuardedBy("<self>") Session session = new Session(); 191 192 /** True if this has been closed. */ 193 private boolean finished = false; 194 195 @Override 196 @SuppressWarnings("nullness") // tricky, but I think it's OK 197 public void run() { 198 debugln("Worker: run"); 199 while (session != null) { 200 synchronized (mgr) { 201 mgr.pending = null; 202 mgr.notifyAll(); 203 try { 204 mgr.wait(0); 205 } catch (InterruptedException e) { 206 // It's OK for a wait() to be interrupted. 207 } 208 assert mgr.pending != null 209 : "@AssumeAssertion(nullness): bug? might not be true if interrupted?"; 210 // session != null && mgr.pending != null; 211 } 212 error = null; 213 try { 214 // session could also be null at this point, I presume. 215 // That's probably what the catch block is for. 216 mgr.pending.apply(session); 217 } catch (Throwable e) { 218 if (finished) { 219 return; 220 } 221 error = e.toString(); 222 e.printStackTrace(); 223 } 224 } 225 } 226 227 @SuppressWarnings({ 228 "nullness:contracts.precondition.override", 229 "builder:required.method.not.called" // operation performed on alias 230 }) 231 @EnsuresCalledMethods(value = "session", methods = "close") 232 @RequiresNonNull("session") 233 @Override 234 public void close(@GuardSatisfied Worker this) { 235 finished = true; 236 final @GuardedBy("<self>") Session tmp = session; 237 synchronized (tmp) { 238 tmp.close(); 239 } 240 session = null; 241 } 242 } 243 244 /** 245 * Entry point for testing. 246 * 247 * @param args command-line arguments 248 * @throws TimeoutException if SessionManager times out 249 */ 250 public static void main(String[] args) throws TimeoutException { 251 daikon.LogHelper.setupLogs(INFO); 252 SessionManager m = null; // dummy initialization to satisfy compiler's definite assignment check 253 try { 254 m = new SessionManager(); 255 CmdCheck cc; 256 257 cc = new CmdCheck("(EQ 1 1)"); 258 m.request(cc); 259 assert cc.valid; 260 261 cc = new CmdCheck("(EQ 1 2)"); 262 m.request(cc); 263 assert !cc.valid; 264 265 cc = new CmdCheck("(EQ x z)"); 266 m.request(cc); 267 assert !cc.valid; 268 269 CmdAssume a = new CmdAssume("(AND (EQ x y) (EQ y z))"); 270 m.request(a); 271 272 m.request(cc); 273 assert cc.valid; 274 275 m.request(CmdUndoAssume.single); 276 277 m.request(cc); 278 assert !cc.valid; 279 280 StringBuilder buf = new StringBuilder(); 281 282 for (int i = 0; i < 20000; i++) { 283 buf.append("(EQ (select a " + i + ") " + (int) (200000 * Math.random()) + ")"); 284 } 285 m.request(new CmdAssume(buf.toString())); 286 287 for (int i = 0; i < 10; i++) { 288 try { 289 m.request(new CmdCheck("(NOT (EXISTS (x) (EQ (select a x) (+ x " + i + "))))")); 290 } catch (TimeoutException e) { 291 System.out.println("Timeout, retrying"); 292 m.close(); 293 m = new SessionManager(); 294 m.request(new CmdAssume(buf.toString())); 295 } 296 } 297 } finally { 298 if (m != null) { 299 m.close(); 300 } 301 } 302 } 303}