|
20 | 20 | import org.elasticsearch.common.bytes.BytesReference;
|
21 | 21 | import org.elasticsearch.common.collect.Iterators;
|
22 | 22 | import org.elasticsearch.common.io.Streams;
|
| 23 | +import org.elasticsearch.common.util.concurrent.KeyedLock; |
23 | 24 | import org.elasticsearch.core.CheckedConsumer;
|
24 | 25 | import org.elasticsearch.core.IOUtils;
|
| 26 | +import org.elasticsearch.core.Releasable; |
25 | 27 | import org.elasticsearch.core.Strings;
|
| 28 | +import org.elasticsearch.core.SuppressForbidden; |
26 | 29 |
|
27 | 30 | import java.io.FileNotFoundException;
|
28 | 31 | import java.io.IOException;
|
29 | 32 | import java.io.InputStream;
|
30 | 33 | import java.io.OutputStream;
|
| 34 | +import java.nio.ByteBuffer; |
31 | 35 | import java.nio.channels.Channels;
|
| 36 | +import java.nio.channels.FileChannel; |
| 37 | +import java.nio.channels.FileLock; |
32 | 38 | import java.nio.channels.SeekableByteChannel;
|
33 | 39 | import java.nio.file.AccessDeniedException;
|
34 | 40 | import java.nio.file.DirectoryStream;
|
@@ -369,4 +375,54 @@ public static boolean isTempBlobName(final String blobName) {
|
369 | 375 | private static OutputStream blobOutputStream(Path file) throws IOException {
|
370 | 376 | return Files.newOutputStream(file, StandardOpenOption.CREATE_NEW);
|
371 | 377 | }
|
| 378 | + |
| 379 | + private static final KeyedLock<String> registerLocks = new KeyedLock<>(); |
| 380 | + |
| 381 | + @Override |
| 382 | + @SuppressForbidden(reason = "write to channel that we have open for locking purposes already directly") |
| 383 | + public long compareAndExchangeRegister(String key, long expected, long updated) throws IOException { |
| 384 | + try ( |
| 385 | + FileChannel channel = openOrCreateAtomic(path.resolve(key)); |
| 386 | + FileLock ignored1 = channel.lock(); |
| 387 | + Releasable ignored2 = registerLocks.acquire(key) |
| 388 | + ) { |
| 389 | + final ByteBuffer buf = ByteBuffer.allocate(Long.BYTES); |
| 390 | + final long found; |
| 391 | + while (buf.remaining() > 0) { |
| 392 | + if (channel.read(buf) == -1) { |
| 393 | + break; |
| 394 | + } |
| 395 | + } |
| 396 | + if (buf.position() == 0) { |
| 397 | + found = 0L; |
| 398 | + } else if (buf.position() == Long.BYTES) { |
| 399 | + found = buf.getLong(0); |
| 400 | + buf.clear(); |
| 401 | + if (channel.read(buf) != -1) { |
| 402 | + throw new IllegalStateException("Read file of length greater than [" + Long.BYTES + "] for [" + key + "]"); |
| 403 | + } |
| 404 | + } else { |
| 405 | + throw new IllegalStateException("Read file of length [" + buf.position() + "] for [" + key + "]"); |
| 406 | + } |
| 407 | + if (found == expected) { |
| 408 | + buf.clear().putLong(updated).flip(); |
| 409 | + while (buf.remaining() > 0) { |
| 410 | + channel.write(buf, buf.position()); |
| 411 | + } |
| 412 | + channel.force(true); |
| 413 | + } |
| 414 | + return found; |
| 415 | + } |
| 416 | + } |
| 417 | + |
| 418 | + private static FileChannel openOrCreateAtomic(Path path) throws IOException { |
| 419 | + try { |
| 420 | + if (Files.exists(path) == false) { |
| 421 | + return FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); |
| 422 | + } |
| 423 | + } catch (FileAlreadyExistsException e) { |
| 424 | + // ok, created concurrently |
| 425 | + } |
| 426 | + return FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.WRITE); |
| 427 | + } |
372 | 428 | }
|
0 commit comments