From 0696d2e7768c2256f8ff60f6f03dd152a785e654 Mon Sep 17 00:00:00 2001
From: Markus Alexander Kuppe <tlaplus.net@lemmster.de>
Date: Tue, 24 Mar 2020 16:46:27 -0700
Subject: [PATCH] Fall back to conservative memory allocation if LiveWorker
 runs into OutOfMemoryError.  If conservative allocation succeeds, liveness
 checking will be slow.

[Feature][Bug]
---
 .../src/tlc2/tool/liveness/LiveWorker.java    | 45 ++++++++++++-------
 .../tlc2/util/SynchronousDiskIntStack.java    |  2 +-
 2 files changed, 30 insertions(+), 17 deletions(-)

diff --git a/tlatools/src/tlc2/tool/liveness/LiveWorker.java b/tlatools/src/tlc2/tool/liveness/LiveWorker.java
index e62ea5068..21f23c196 100644
--- a/tlatools/src/tlc2/tool/liveness/LiveWorker.java
+++ b/tlatools/src/tlc2/tool/liveness/LiveWorker.java
@@ -477,24 +477,37 @@ public class LiveWorker implements Callable<Boolean> {
 		// memory. Also take the total number of simultaneously running
 		// workers into account that have to share the available memory
 		// among each other.
-		final double freeMemoryInBytes = (Runtime.getRuntime().freeMemory() / (numWorkers * 1d));
-		final long graphSizeInBytes = this.dg.getSizeOnDisk();
-		final double ratio = graphSizeInBytes / freeMemoryInBytes;
-		if (ratio > TLCGlobals.livenessGraphSizeThreshold) {
-			// Double SDIS's bufSize/pageSize by how much the graph size
-			// overshoots the free memory size, but limit page size to 1gb.
-			// Also, don't allocate more than what is available.
-			final int capacityInBytes = SynchronousDiskIntStack.BufSize << Math.min((int) ratio, 5);
-			if (capacityInBytes < freeMemoryInBytes) {
-				return new SynchronousDiskIntStack(metaDir, name, capacityInBytes);
-			} else {
-				// Use default SDIS which is 32mb of in-memory size
-				return new SynchronousDiskIntStack(metaDir, name);
+		try {
+			final double freeMemoryInBytes = (Runtime.getRuntime().freeMemory() / (numWorkers * 1d));
+			final long graphSizeInBytes = this.dg.getSizeOnDisk();
+			final double ratio = graphSizeInBytes / freeMemoryInBytes;
+			if (ratio > TLCGlobals.livenessGraphSizeThreshold) {
+				// Double SDIS's bufSize/pageSize by how much the graph size
+				// overshoots the free memory size, but limit page size to 1gb.
+				// Also, don't allocate more than what is available.
+				final int capacityInBytes = SynchronousDiskIntStack.BufSize << Math.min((int) ratio, 5);
+				if (capacityInBytes < freeMemoryInBytes) {
+					return new SynchronousDiskIntStack(metaDir, name, capacityInBytes);
+				} else {
+					// Use default SDIS which is 32mb of in-memory size
+					return new SynchronousDiskIntStack(metaDir, name);
+				}
 			}
+			// If the disk graph as a whole fits into memory, do not use a
+			// disk-backed SynchronousDiskIntStack.
+			return new MemIntStack(metaDir, name);
+		} catch (final OutOfMemoryError oom) {
+			System.gc();
+			// If the allocation above failed, be more conservative. If it fails to
+			// allocate even 16mb, TLC will subsequently terminate with a message about insufficient
+			// memory. 
+			final SynchronousDiskIntStack sdis = new SynchronousDiskIntStack(metaDir, name,
+					SynchronousDiskIntStack.BufSize / 2);
+			MP.printWarning(EC.GENERAL,
+					"Liveness checking will be extremely slow because TLC is running low on memory.\n"
+							+ "Try allocating more memory to the Java pool (heap) in future TLC runs.");
+			return sdis;
 		}
-		// If the disk graph as a whole fits into memory, do not use a
-		// disk-backed SynchronousDiskIntStack.
-		return new MemIntStack(metaDir, name);
 	}
 
 	/**
diff --git a/tlatools/src/tlc2/util/SynchronousDiskIntStack.java b/tlatools/src/tlc2/util/SynchronousDiskIntStack.java
index 15c34bfea..b66120433 100644
--- a/tlatools/src/tlc2/util/SynchronousDiskIntStack.java
+++ b/tlatools/src/tlc2/util/SynchronousDiskIntStack.java
@@ -55,7 +55,7 @@ public class SynchronousDiskIntStack implements IntStack {
 	
 	public SynchronousDiskIntStack(String diskdir, String name, int capacity) {
 		// Hard-limit capacity to 1gb per page file
-		capacity = Math.min(BufSizeMax, Math.max(capacity, BufSize));
+		capacity = Math.min(BufSizeMax, capacity);
 		this.filePrefix = diskdir + FileUtil.separator + name;
 		this.bufSize = capacity;
 		this.buf = new int[capacity];
-- 
GitLab