Skip to content

Commit

Permalink
Refactor transaction log managers to use singleton pattern
Browse files Browse the repository at this point in the history
  • Loading branch information
dsplayerX committed Jun 6, 2024
1 parent 45e30eb commit 393d187
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
import io.ballerina.runtime.internal.diagnostics.RuntimeDiagnosticLog;
import io.ballerina.runtime.internal.errors.ErrorCodes;
import io.ballerina.runtime.internal.util.RuntimeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.File;
Expand All @@ -35,6 +33,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

Expand All @@ -46,9 +45,8 @@
*
* @since 2201.9.0
*/
public class FileRecoveryLog implements RecoveryLog {
public final class FileRecoveryLog implements RecoveryLog {

private static final Logger log = LoggerFactory.getLogger(FileRecoveryLog.class);
private static final String LOG_FILE_NUMBER = "(\\d+)";
private static final String LOG_FILE_EXTENSION = ".log";
private final String baseFileName;
Expand All @@ -61,6 +59,7 @@ public class FileRecoveryLog implements RecoveryLog {
private Map<String, TransactionLogRecord> existingLogs;
private static final PrintStream stderr = System.err;
private final RuntimeDiagnosticLog diagnosticLog = new RuntimeDiagnosticLog();
private static FileRecoveryLog instance;

/**
* Initializes a new FileRecoveryLog instance with the given base file name.
Expand All @@ -70,16 +69,25 @@ public class FileRecoveryLog implements RecoveryLog {
* @param recoveryLogDir The directory to store the recovery log files in.
* @param deleteOldLogs Whether to delete old log files when creating a new one.
*/
public FileRecoveryLog(String baseFileName, int checkpointInterval, Path recoveryLogDir, boolean deleteOldLogs) {
private FileRecoveryLog(String baseFileName, int checkpointInterval, Path recoveryLogDir, boolean deleteOldLogs) {
this.baseFileName = baseFileName;
this.recoveryLogDir = recoveryLogDir;
this.deleteOldLogs = deleteOldLogs;
this.checkpointInterval = checkpointInterval;
this.existingLogs = new HashMap<>();
this.logFile = createNextVersion();
this.numOfPutsSinceLastCheckpoint = 0;
}

public static FileRecoveryLog getInstance(String baseFileName, int checkpointInterval, Path recoveryLogDir,
boolean deleteOldLogs) {
if (instance != null) {
throw new IllegalStateException("instance already exists");

Check warning on line 84 in bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/transactions/FileRecoveryLog.java

View check run for this annotation

Codecov / codecov/patch

bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/transactions/FileRecoveryLog.java#L84

Added line #L84 was not covered by tests
}
instance = new FileRecoveryLog(baseFileName, checkpointInterval, recoveryLogDir, deleteOldLogs);
instance.logFile = instance.createNextVersion();
return instance;
}

/**
* Creates the next version of the recovery log file, cleaning up the previous one.
*
Expand Down Expand Up @@ -194,9 +202,6 @@ public void putAll(Map<String, TransactionLogRecord> trxRecords) {
public Map<String, TransactionLogRecord> getPendingLogs() {
Map<String, TransactionLogRecord> pendingTransactions = new HashMap<>();
Map<String, TransactionLogRecord> transactionLogs = readLogsFromFile(logFile);
if (transactionLogs == null) {
return null;
}
for (Map.Entry<String, TransactionLogRecord> entry : transactionLogs.entrySet()) {
String trxId = entry.getKey();
TransactionLogRecord trxRecord = entry.getValue();
Expand Down Expand Up @@ -234,7 +239,7 @@ private void writeToFile(String str, boolean force) {
*/
private Map<String, TransactionLogRecord> readLogsFromFile(File file) {
if (!file.exists() || file.length() == 0) {
return null;
return Collections.emptyMap();
}
if (fileLockAndChannel != null) {
closeEverything();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,20 @@
*
* @since 2201.9.0
*/
public class InMemoryRecoveryLog implements RecoveryLog {
public final class InMemoryRecoveryLog implements RecoveryLog {

private final Map<String, TransactionLogRecord> transactionLogs;
private int numOfPutsSinceLastCheckpoint;
private final Map<String, TransactionLogRecord> transactionLogs = new ConcurrentHashMap<>();
private int numOfPutsSinceLastCheckpoint = 0;
private static InMemoryRecoveryLog instance;

public InMemoryRecoveryLog() {
this.transactionLogs = new ConcurrentHashMap<>();
this.numOfPutsSinceLastCheckpoint = 0;
private InMemoryRecoveryLog() {
}

public static InMemoryRecoveryLog getInstance() {
if (instance == null) {
instance = new InMemoryRecoveryLog();
}
return instance;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@
* @since 2201.9.0
*/
public class LogManager {

private final FileRecoveryLog fileRecoveryLog;
private final InMemoryRecoveryLog inMemoryRecoveryLog;
private static LogManager instance;

/**
* Create a log manager with the given base file name, checkpoint interval, recovery log directory, and delete old
Expand All @@ -39,12 +41,21 @@ public class LogManager {
* @param recoveryLogDir the recovery log directory
* @param deleteOldLogs the delete old logs flag
*/
public LogManager(String baseFileName, int checkpointInterval, Path recoveryLogDir, boolean deleteOldLogs) {
this.fileRecoveryLog = new FileRecoveryLog(baseFileName, checkpointInterval, recoveryLogDir, deleteOldLogs);
this.inMemoryRecoveryLog = new InMemoryRecoveryLog();
private LogManager(String baseFileName, int checkpointInterval, Path recoveryLogDir, boolean deleteOldLogs) {
this.fileRecoveryLog =
FileRecoveryLog.getInstance(baseFileName, checkpointInterval, recoveryLogDir, deleteOldLogs);
this.inMemoryRecoveryLog = InMemoryRecoveryLog.getInstance();
init();
}

public static LogManager getInstance(String baseFileName, int checkpointInterval, Path recoveryLogDir,
boolean deleteOldLogs) {
if (instance == null) {
instance = new LogManager(baseFileName, checkpointInterval, recoveryLogDir, deleteOldLogs);
}
return instance;
}

private void init() {
// Read pending transactions from the file recovery log and add them to the in-memory log.
Map<String, TransactionLogRecord> pendingTransactions = fileRecoveryLog.getPendingLogs();
Expand Down Expand Up @@ -75,5 +86,6 @@ public Map<String, TransactionLogRecord> getFailedTransactionLogs() {
*/
public void close() {
fileRecoveryLog.close();
inMemoryRecoveryLog.close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ private TransactionResourceManager() {
userTransactionManager = new UserTransactionManager();
} else {
xidRegistry = new HashMap<>();
logManager = new LogManager(getRecoveryLogBaseName(), getCheckpointInterval(),
logManager = LogManager.getInstance(getRecoveryLogBaseName(), getCheckpointInterval(),
getRecoveryLogDir(), getDeleteOldLogs());
recoveryManager = new RecoveryManager();
if (!diagnosticLog.getDiagnosticList().isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
import io.ballerina.runtime.transactions.RecoveryState;
import io.ballerina.runtime.transactions.TransactionLogRecord;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.Test;

import java.io.IOException;
Expand All @@ -40,14 +40,14 @@ public class LogManagerTests {
private TransactionLogRecord logRecord;
private Path recoveryLogDir = Path.of("build/tmp/test/recovery/logManagerTestLogs");

@BeforeMethod
@BeforeSuite
public void setup() {
logManager = new LogManager("baseFileName", -1, recoveryLogDir, false);
logManager = LogManager.getInstance("testLog", -1, recoveryLogDir, false);
logRecord = new TransactionLogRecord("00000000-0000-0000-0000-000000000000", "0",
RecoveryState.PREPARING);
}

@Test (description = "Test adding logs records to both in-memory and file recovery logs")
@Test(description = "Test adding logs records to both in-memory and file recovery logs")
public void testPut() throws Exception {
logManager.put(logRecord);
Field inMemoryRecoveryLogField = LogManager.class.getDeclaredField("inMemoryRecoveryLog");
Expand All @@ -63,14 +63,14 @@ public void testPut() throws Exception {
Assert.assertTrue(fileLogs.containsKey(logRecord.getCombinedId()));
}

@Test (description = "Test getting failed transaction logs from the log manager")
@Test(description = "Test getting failed transaction logs from the log manager")
public void testGetFailedTransactionLogs() {
logManager.put(logRecord);
Map<String, TransactionLogRecord> failedTransactions = logManager.getFailedTransactionLogs();
Assert.assertTrue(failedTransactions.containsValue(logRecord));
}

@AfterMethod
@AfterSuite
public void tearDown() throws IOException {
logManager.close();
if (Files.exists(recoveryLogDir)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public void setup() throws NoSuchFieldException, IllegalAccessException {
recoveryManager = new RecoveryManager();
Field logManagerField = TransactionResourceManager.class.getDeclaredField("logManager");
logManagerField.setAccessible(true);
LogManager logManager = new LogManager("testLog", -1,
LogManager logManager = LogManager.getInstance("testLog", -1,
Path.of("build/tmp/test/recovery/testRecoveryLogs"), true);
logManagerField.set(transactionResourceManager, logManager);

Expand Down Expand Up @@ -339,6 +339,9 @@ public void testHeuristicTerminationWithHuerHazard()
ErrorCodes.TRANSACTION_IN_HUERISTIC_STATE.diagnosticId());
}

@Test (description = "Test unable to create file")


@AfterTest
public void tearDown() throws IOException {
Path dir = Paths.get("build/tmp/test/recovery/testRecoveryLogs");
Expand Down

0 comments on commit 393d187

Please sign in to comment.