forked from runtimeverification/k
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathBinaryLoader.java
More file actions
157 lines (142 loc) · 6.78 KB
/
BinaryLoader.java
File metadata and controls
157 lines (142 loc) · 6.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// Copyright (c) 2013-2019 K Team. All Rights Reserved.
package org.kframework.utils;
import com.google.inject.Inject;
import jline.internal.Nullable;
import org.kframework.utils.errorsystem.KEMException;
import org.kframework.utils.errorsystem.KExceptionManager;
import org.kframework.utils.inject.RequestScoped;
import org.nustaq.serialization.FSTObjectInput;
import org.nustaq.serialization.FSTObjectOutput;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectStreamException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.channels.OverlappingFileLockException;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@RequestScoped
public class BinaryLoader {
static {
disableAccessWarnings();
}
/**
* Disables JVM warnings "WARNING: An illegal reflective access operation has occurred" produced by FST.
* <p>
* source: https://stackoverflow.com/a/53517025/4182868
*/
@SuppressWarnings("unchecked")
public static void disableAccessWarnings() {
try {
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Object unsafe = field.get(null);
Method putObjectVolatile =
unsafeClass.getDeclaredMethod("putObjectVolatile", Object.class, long.class, Object.class);
Method staticFieldOffset = unsafeClass.getDeclaredMethod("staticFieldOffset", Field.class);
Class loggerClass = Class.forName("jdk.internal.module.IllegalAccessLogger");
Field loggerField = loggerClass.getDeclaredField("logger");
Long offset = (Long) staticFieldOffset.invoke(unsafe, loggerField);
putObjectVolatile.invoke(unsafe, loggerClass, offset, null);
// DISABLE EXCEPTION CHECKSTYLE
} catch (Exception ignored) {
// ENABLE EXCEPTION CHECKSTYLE
//Exception here caused by reflexion will just re-enable the warning.
}
}
private static ReadWriteLock lock = new ReentrantReadWriteLock();
private final KExceptionManager kem;
@Inject
public BinaryLoader(KExceptionManager kem) {
this.kem = kem;
}
public void saveOrDie(File file, Object o) {
File dir = file.getAbsoluteFile().getParentFile();
if (!dir.exists() && !dir.mkdirs()) {
throw KEMException.criticalError("Could not create directory " + dir);
}
try {
saveSynchronized(file, o);
} catch (IOException e) {
throw KEMException.criticalError("Could not write to " + file.getAbsolutePath(), e);
} catch (InterruptedException e) {
throw KEMException.criticalError("Interrupted while locking to write " + file, e);
}
}
public <T> T loadOrDie(Class<T> cls, File file) {
try {
return cls.cast(loadSynchronized(file));
} catch (ClassNotFoundException e) {
throw new AssertionError("Something wrong with deserialization", e);
} catch (ObjectStreamException e) {
throw KEMException.criticalError("Kompiled definition is out of date with "
+ "the latest version of the K tool. Please re-run kompile and try again.", e);
} catch (IOException e) {
throw KEMException.criticalError("Could not read from " + file.getAbsolutePath(), e);
} catch (InterruptedException e) {
throw KEMException.criticalError("Interrupted while locking to read " + file.getAbsolutePath(), e);
}
}
@Nullable
public <T> T loadCache(Class<T> cls, File file) {
try {
return cls.cast(loadSynchronized(file));
} catch (FileNotFoundException e) {
//ignored
} catch (IOException | ClassNotFoundException e) {
kem.registerInternalHiddenWarning("Invalidating serialized cache due to corruption.", e);
} catch (InterruptedException e) {
throw KEMException.criticalError("Interrupted while locking to read " + file.getAbsolutePath(), e);
}
return null;
}
/**
* Locks the file before writing, so that it cannot be read by another instance of K. If the file is currently in
* use, this method will block until lock can be acquired.
*/
public void saveSynchronized(File file, Object o) throws IOException, InterruptedException {
//To protect from concurrent access from another thread, in kserver mode
lock.writeLock().lockInterruptibly();
//JDK API limitation: there's no API to atomically open a file for writing and lock it.
//Consequently, if another process reads a file between the moments this thread opens a stream and acquires a
// lock, it will see an empty file.
// To prevent this we acquire file lock before opening the stream, using another stream.
try (FileOutputStream lockStream = new FileOutputStream(file, true)) {
//To protect from concurrent access to same file from another process, in standalone mode
lockStream.getChannel().lock(); //Lock is released automatically when lockStream is closed.
try (FSTObjectOutput serializer = new FSTObjectOutput(new FileOutputStream(file))) { //already buffered
serializer.writeObject(o);
}
} finally {
lock.writeLock().unlock();
}
}
public Object loadSynchronized(File file) throws IOException, ClassNotFoundException, InterruptedException {
//To protect from concurrent access from another thread
lock.readLock().lockInterruptibly();
//There's no issue if input stream is opened before lock is acquired
try (FileInputStream in = new FileInputStream(file)) {
//To protect from concurrent access to same file from another process
//Lock is released automatically when stream is closed.
try {
in.getChannel().lock(0L, Long.MAX_VALUE, true);
} catch (OverlappingFileLockException e) {
//We are in Nailgun mode. File lock is not needed.
}
try (FSTObjectInput deserializer = new FSTObjectInput(in)) { //already buffered
Object obj = deserializer.readObject();
return obj;
} finally {
// Former FST bug workaround: FSTObjectInput.close() doesn't close underlying FileInputStream.
// This may cause OverlappingFileLockException in saveSynchronized(), in Nailgun mode.
in.close();
}
} finally {
lock.readLock().unlock();
}
}
}