SnapshotMonitorTest.java

/*******************************************************************************
 * Copyright (c) 2022 Eclipse RDF4J contributors.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Distribution License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *******************************************************************************/

package org.eclipse.rdf4j.sail.memory;

import org.eclipse.rdf4j.common.iteration.CloseableIteration;
import org.eclipse.rdf4j.common.transaction.IsolationLevels;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.sail.base.SailDataset;
import org.eclipse.rdf4j.sail.base.SailSink;
import org.eclipse.rdf4j.sail.base.SailSource;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;

public class SnapshotMonitorTest {

	@Test
	@Timeout(60)
	public void testAutomaticCleanupDataset() throws InterruptedException {
		try (MemorySailStore memorySailStore = new MemorySailStore(false)) {

			try (SailSource explicitSailSource = memorySailStore.getExplicitSailSource()) {
				getAndAbandonDataset(explicitSailSource, memorySailStore.snapshotMonitor);

				memorySailStore.snapshotMonitor.reserve(100, this).release();

				while (memorySailStore.snapshotMonitor.getFirstUnusedOrElse(100) != 99) {
					System.gc();
					Thread.sleep(1);
				}
			}

		}

	}

	@Test
	@Timeout(60)
	public void testAutomaticCleanupSink() throws InterruptedException {
		try (MemorySailStore memorySailStore = new MemorySailStore(false)) {

			try (SailSource explicitSailSource = memorySailStore.getExplicitSailSource()) {
				getAndAbandonSink(explicitSailSource, memorySailStore.snapshotMonitor);

				memorySailStore.snapshotMonitor.reserve(100, this).release();

				while (memorySailStore.snapshotMonitor.getFirstUnusedOrElse(100) != 99) {
					System.gc();
					Thread.sleep(1);
				}
			}

		}

	}

	@Test
	public void testReservationAndReleaseDataset() {
		try (MemorySailStore memorySailStore = new MemorySailStore(false)) {

			try (SailSource explicitSailSource = memorySailStore.getExplicitSailSource()) {
				try (SailDataset dataset = explicitSailSource.dataset(IsolationLevels.SNAPSHOT)) {
					Assertions.assertEquals(-1,
							memorySailStore.snapshotMonitor.getFirstUnusedOrElse(100));
				}

				memorySailStore.snapshotMonitor.reserve(100, this).release();

				Assertions.assertEquals(99,
						memorySailStore.snapshotMonitor.getFirstUnusedOrElse(100));
			}

		}

	}

	@Test
	public void testReservationAndReleaseDatasetNone() {
		try (MemorySailStore memorySailStore = new MemorySailStore(false)) {

			try (SailSource explicitSailSource = memorySailStore.getExplicitSailSource()) {
				try (SailDataset dataset = explicitSailSource.dataset(IsolationLevels.NONE)) {
					Assertions.assertEquals(99,
							memorySailStore.snapshotMonitor.getFirstUnusedOrElse(100));
				}
			}

		}

	}

	@Test
	public void testReservationAndReleaseSinkSerializable() {
		try (MemorySailStore memorySailStore = new MemorySailStore(false)) {

			try (SailSource explicitSailSource = memorySailStore.getExplicitSailSource()) {
				try (SailSink sink = explicitSailSource.sink(IsolationLevels.SERIALIZABLE)) {
					Assertions.assertEquals(-1,
							memorySailStore.snapshotMonitor.getFirstUnusedOrElse(100));
				}
				memorySailStore.snapshotMonitor.reserve(100, this).release();

				Assertions.assertEquals(99,
						memorySailStore.snapshotMonitor.getFirstUnusedOrElse(100));
			}

		}

	}

	@Test
	public void testReservationAndReleaseSink() {
		try (MemorySailStore memorySailStore = new MemorySailStore(false)) {

			try (SailSource explicitSailSource = memorySailStore.getExplicitSailSource()) {
				try (SailSink sink = explicitSailSource.sink(IsolationLevels.SNAPSHOT)) {
					Assertions.assertEquals(-1,
							memorySailStore.snapshotMonitor.getFirstUnusedOrElse(0));
				}
			}

		}

	}

	@Test
	public void testMultipleReservations() {
		try (MemorySailStore memorySailStore = new MemorySailStore(true)) {

			try (SailSource explicitSailSource = memorySailStore.getExplicitSailSource()) {
				try (SailDataset dataset = explicitSailSource.dataset(IsolationLevels.SNAPSHOT)) {
					Assertions.assertEquals(-1,
							memorySailStore.snapshotMonitor.getFirstUnusedOrElse(100));
				}
				try (SailSink sink = explicitSailSource.sink(IsolationLevels.SNAPSHOT)) {
					sink.prepare();
					sink.flush();
				}
				try (SailDataset dataset = explicitSailSource.dataset(IsolationLevels.SNAPSHOT)) {
					try (SailSink sink = explicitSailSource.sink(IsolationLevels.SNAPSHOT)) {
						sink.prepare();
						sink.flush();
					}
					Assertions.assertEquals(0, memorySailStore.snapshotMonitor.getFirstUnusedOrElse(100));
					try (SailDataset dataset2 = explicitSailSource.dataset(IsolationLevels.SNAPSHOT)) {
						try (SailSink sink = explicitSailSource.sink(IsolationLevels.SNAPSHOT)) {
							sink.prepare();
							sink.flush();
						}
						Assertions.assertEquals(0,
								memorySailStore.snapshotMonitor.getFirstUnusedOrElse(100));
					}
				}

				try (SailDataset dataset = explicitSailSource.dataset(IsolationLevels.SNAPSHOT)) {
					Assertions.assertEquals(2, memorySailStore.snapshotMonitor.getFirstUnusedOrElse(100));
				}

				memorySailStore.snapshotMonitor.reserve(100, this).release();

				Assertions.assertEquals(99,
						memorySailStore.snapshotMonitor.getFirstUnusedOrElse(100));
			}

		}

	}

	@Test
	public void testOverlappingReservations() {
		try (MemorySailStore memorySailStore = new MemorySailStore(true)) {

			try (SailSource explicitSailSource = memorySailStore.getExplicitSailSource()) {
				SailDataset dataset = explicitSailSource.dataset(IsolationLevels.SNAPSHOT);

				SailSink sink = explicitSailSource.sink(IsolationLevels.SNAPSHOT);
				sink.prepare();
				sink.flush();
				sink.close();

				sink = explicitSailSource.sink(IsolationLevels.SNAPSHOT);
				sink.prepare();
				sink.flush();
				sink.close();

				sink = explicitSailSource.sink(IsolationLevels.SERIALIZABLE);
				Assertions.assertEquals(-1, memorySailStore.snapshotMonitor.getFirstUnusedOrElse(100));

				dataset.close();

				Assertions.assertEquals(1, memorySailStore.snapshotMonitor.getFirstUnusedOrElse(100));

				sink.prepare();
				sink.flush();

				dataset = explicitSailSource.dataset(IsolationLevels.SNAPSHOT);

				Assertions.assertEquals(1, memorySailStore.snapshotMonitor.getFirstUnusedOrElse(100));

				sink.close();

				Assertions.assertEquals(2, memorySailStore.snapshotMonitor.getFirstUnusedOrElse(100));

				dataset.close();
				memorySailStore.snapshotMonitor.reserve(100, this).release();

				Assertions.assertEquals(99,
						memorySailStore.snapshotMonitor.getFirstUnusedOrElse(100));

			}

		}

	}

	private void getAndAbandonDataset(SailSource explicitSailSource, MemorySailStore.SnapshotMonitor snapshotMonitor) {
		SailDataset dataset = explicitSailSource.dataset(IsolationLevels.SNAPSHOT);
		CloseableIteration<? extends Resource> contextIDs = dataset.getContextIDs();
		contextIDs.close();
		Assertions.assertNotEquals(100, snapshotMonitor.getFirstUnusedOrElse(100));
	}

	private void getAndAbandonSink(SailSource explicitSailSource, MemorySailStore.SnapshotMonitor snapshotMonitor) {
		SailSink sink = explicitSailSource.sink(IsolationLevels.SERIALIZABLE);
		sink.prepare();
		sink.flush();
		Assertions.assertNotEquals(100, snapshotMonitor.getFirstUnusedOrElse(100));
	}
}