exclusive file locking class and tests

use new bin/ruby and bundler without :development

refactor to DRY and use expected exception

added original Apache 2.0 license and some cosmetics

exclude bin/lock from packaging

rename variables
This commit is contained in:
Colin Surprenant 2017-01-30 16:52:03 -05:00
parent b3906c89cd
commit d79e3730fe
5 changed files with 174 additions and 0 deletions

9
bin/lock Executable file
View file

@ -0,0 +1,9 @@
#!/usr/bin/env bin/ruby
require_relative "../lib/bootstrap/environment"
LogStash::Bundler.setup!({:without => [:build, :development]})
require "logstash-core"
lock = Java::OrgLogstash::FileLockFactory.getDefault.obtainLock(ARGV[0], ".lock")
puts("locking " + File.join(ARGV[0], ".lock"))
sleep

View file

@ -0,0 +1,98 @@
// this class is largely inspired by Lucene FSLockFactory and friends, below is the Lucene original Apache 2.0 license:
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.logstash;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class FileLockFactory {
/**
* Singleton instance
*/
public static final FileLockFactory INSTANCE = new FileLockFactory();
private FileLockFactory() {}
private static final Set<String> LOCK_HELD = Collections.synchronizedSet(new HashSet<String>());
public static final FileLockFactory getDefault() {
return FileLockFactory.INSTANCE;
}
public FileLock obtainLock(String lockDir, String lockName) throws IOException {
Path dirPath = FileSystems.getDefault().getPath(lockDir);
// Ensure that lockDir exists and is a directory.
// note: this will fail if lockDir is a symlink
Files.createDirectories(dirPath);
Path lockPath = dirPath.resolve(lockName);
try {
Files.createFile(lockPath);
} catch (IOException ignore) {
// we must create the file to have a truly canonical path.
// if it's already created, we don't care. if it cant be created, it will fail below.
}
// fails if the lock file does not exist
final Path realLockPath = lockPath.toRealPath();
if (LOCK_HELD.add(realLockPath.toString())) {
FileChannel channel = null;
FileLock lock = null;
try {
channel = FileChannel.open(realLockPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
lock = channel.tryLock();
if (lock != null) {
return lock;
} else {
throw new LockException("Lock held by another program: " + realLockPath);
}
} finally {
if (lock == null) { // not successful - clear up and move out
try {
if (channel != null) {
channel.close();
}
} catch (Throwable t) {
// suppress any channel close exceptions
}
boolean remove = LOCK_HELD.remove(realLockPath.toString());
if (remove == false) {
throw new LockException("Lock path was cleared but never marked as held: " + realLockPath);
}
}
}
} else {
throw new LockException("Lock held by this virtual machine: " + realLockPath);
}
}
}

View file

@ -0,0 +1,13 @@
package org.logstash;
import java.io.IOException;
public class LockException extends IOException {
public LockException(String message) {
super(message);
}
public LockException(String message, Throwable cause) {
super(message, cause);
}
}

View file

@ -0,0 +1,53 @@
package org.logstash;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.nio.channels.FileLock;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
public class FileLockFactoryTest {
@Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
private String lockDir;
private final String LOCK_FILE = ".test";
@Before
public void setUp() throws Exception {
lockDir = temporaryFolder.newFolder("lock").getPath();
}
@Before
public void lockFirst() throws Exception {
FileLock lock = FileLockFactory.getDefault().obtainLock(lockDir, LOCK_FILE);
assertThat(lock.isValid(), is(equalTo(true)));
assertThat(lock.isShared(), is(equalTo(false)));
}
@Test
public void ObtainLockOnNonLocked() throws IOException {
// empty to just test the lone @Before lockFirst() test
}
@Test(expected = LockException.class)
public void ObtainLockOnLocked() throws IOException {
FileLockFactory.getDefault().obtainLock(lockDir, LOCK_FILE);
}
@Test
public void ObtainLockOnOtherLocked() throws IOException {
FileLock lock2 = FileLockFactory.getDefault().obtainLock(lockDir, ".test2");
assertThat(lock2.isValid(), is(equalTo(true)));
assertThat(lock2.isShared(), is(equalTo(false)));
}
}

View file

@ -60,6 +60,7 @@ namespace "artifact" do
@exclude_paths << "bin/bundle"
@exclude_paths << "bin/rspec"
@exclude_paths << "bin/rspec.bat"
@exclude_paths << "bin/lock"
@exclude_paths
end