1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.appender;
18
19 import java.io.File;
20 import java.io.IOException;
21 import java.io.OutputStream;
22 import java.io.RandomAccessFile;
23 import java.io.Serializable;
24 import java.lang.reflect.Method;
25 import java.nio.ByteOrder;
26 import java.nio.MappedByteBuffer;
27 import java.nio.channels.FileChannel;
28 import java.security.AccessController;
29 import java.security.PrivilegedActionException;
30 import java.security.PrivilegedExceptionAction;
31 import java.util.HashMap;
32 import java.util.Map;
33
34 import org.apache.logging.log4j.core.Layout;
35 import org.apache.logging.log4j.core.util.Assert;
36 import org.apache.logging.log4j.core.util.Closer;
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51 public class MemoryMappedFileManager extends OutputStreamManager {
52 static final int DEFAULT_REGION_LENGTH = 32 * 1024 * 1024;
53 private static final MemoryMappedFileManagerFactory FACTORY = new MemoryMappedFileManagerFactory();
54
55 private final boolean isForce;
56 private final int regionLength;
57 private final String advertiseURI;
58 private final RandomAccessFile randomAccessFile;
59 private final ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<Boolean>();
60 private MappedByteBuffer mappedBuffer;
61 private long mappingOffset;
62
63 protected MemoryMappedFileManager(final RandomAccessFile file, final String fileName, final OutputStream os,
64 final boolean force, final long position, final int regionLength, final String advertiseURI,
65 final Layout<? extends Serializable> layout) throws IOException {
66 super(os, fileName, layout);
67 this.isForce = force;
68 this.randomAccessFile = Assert.requireNonNull(file, "RandomAccessFile");
69 this.regionLength = regionLength;
70 this.advertiseURI = advertiseURI;
71 this.isEndOfBatch.set(Boolean.FALSE);
72 this.mappedBuffer = mmap(randomAccessFile.getChannel(), position, regionLength);
73 this.mappingOffset = position;
74 }
75
76
77
78
79
80
81
82
83
84
85
86
87 public static MemoryMappedFileManager getFileManager(final String fileName, final boolean append,
88 final boolean isForce, final int regionLength, final String advertiseURI,
89 final Layout<? extends Serializable> layout) {
90 return (MemoryMappedFileManager) getManager(fileName, new FactoryData(append, isForce, regionLength,
91 advertiseURI, layout), FACTORY);
92 }
93
94 public Boolean isEndOfBatch() {
95 return isEndOfBatch.get();
96 }
97
98 public void setEndOfBatch(final boolean isEndOfBatch) {
99 this.isEndOfBatch.set(Boolean.valueOf(isEndOfBatch));
100 }
101
102 @Override
103 protected synchronized void write(final byte[] bytes, int offset, int length) {
104 super.write(bytes, offset, length);
105
106 while (length > mappedBuffer.remaining()) {
107 final int chunk = mappedBuffer.remaining();
108 mappedBuffer.put(bytes, offset, chunk);
109 offset += chunk;
110 length -= chunk;
111 remap();
112 }
113 mappedBuffer.put(bytes, offset, length);
114
115
116
117 }
118
119 private synchronized void remap() {
120 final long offset = this.mappingOffset + mappedBuffer.position();
121 final int length = mappedBuffer.remaining() + regionLength;
122 try {
123 unsafeUnmap(mappedBuffer);
124 final long fileLength = randomAccessFile.length() + regionLength;
125 randomAccessFile.setLength(fileLength);
126 mappedBuffer = mmap(randomAccessFile.getChannel(), offset, length);
127 mappingOffset = offset;
128 } catch (final Exception ex) {
129 LOGGER.error("Unable to remap " + getName() + ". " + ex);
130 }
131 }
132
133 @Override
134 public synchronized void flush() {
135 mappedBuffer.force();
136 }
137
138 @Override
139 public synchronized void close() {
140 final long length = mappingOffset + mappedBuffer.position();
141 try {
142 unsafeUnmap(mappedBuffer);
143 } catch (final Exception ex) {
144 LOGGER.error("Unable to unmap MappedBuffer " + getName() + ". " + ex);
145 }
146 try {
147 randomAccessFile.setLength(length);
148 randomAccessFile.close();
149 } catch (final IOException ex) {
150 LOGGER.error("Unable to close MemoryMappedFile " + getName() + ". " + ex);
151 }
152 }
153
154 public static MappedByteBuffer mmap(final FileChannel fileChannel, final long start, final int size) throws IOException {
155 for (int i = 1;; i++) {
156 try {
157 final MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, start, size);
158 map.order(ByteOrder.nativeOrder());
159 return map;
160 } catch (final IOException e) {
161 if (e.getMessage() == null || !e.getMessage().endsWith("user-mapped section open")) {
162 throw e;
163 }
164 if (i < 10) {
165 Thread.yield();
166 } else {
167 try {
168 Thread.sleep(1);
169 } catch (final InterruptedException ignored) {
170 Thread.currentThread().interrupt();
171 throw e;
172 }
173 }
174 }
175 }
176 }
177
178 private static void unsafeUnmap(final MappedByteBuffer mbb) throws PrivilegedActionException {
179 AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
180 @Override
181 public Object run() throws Exception {
182 final Method getCleanerMethod = mbb.getClass().getMethod("cleaner");
183 getCleanerMethod.setAccessible(true);
184 final Object cleaner = getCleanerMethod.invoke(mbb);
185 final Method cleanMethod = cleaner.getClass().getMethod("clean");
186 cleanMethod.invoke(cleaner);
187 return null;
188 }
189 });
190 }
191
192
193
194
195
196
197 public String getFileName() {
198 return getName();
199 }
200
201
202
203
204
205
206 public int getRegionLength() {
207 return regionLength;
208 }
209
210
211
212
213
214
215 public boolean isImmediateFlush() {
216 return isForce;
217 }
218
219
220 static class DummyOutputStream extends OutputStream {
221 @Override
222 public void write(final int b) throws IOException {
223 }
224
225 @Override
226 public void write(final byte[] b, final int off, final int len) throws IOException {
227 }
228 }
229
230
231
232
233
234
235
236
237
238 @Override
239 public Map<String, String> getContentFormat() {
240 final Map<String, String> result = new HashMap<String, String>(super.getContentFormat());
241 result.put("fileURI", advertiseURI);
242 return result;
243 }
244
245
246
247
248 private static class FactoryData {
249 private final boolean append;
250 private final boolean force;
251 private final int regionLength;
252 private final String advertiseURI;
253 private final Layout<? extends Serializable> layout;
254
255
256
257
258
259
260
261
262 public FactoryData(final boolean append, final boolean force, final int regionLength,
263 final String advertiseURI, final Layout<? extends Serializable> layout) {
264 this.append = append;
265 this.force = force;
266 this.regionLength = regionLength;
267 this.advertiseURI = advertiseURI;
268 this.layout = layout;
269 }
270 }
271
272
273
274
275 private static class MemoryMappedFileManagerFactory implements ManagerFactory<MemoryMappedFileManager, FactoryData> {
276
277
278
279
280
281
282
283
284 @SuppressWarnings("resource")
285 @Override
286 public MemoryMappedFileManager createManager(final String name, final FactoryData data) {
287 final File file = new File(name);
288 final File parent = file.getParentFile();
289 if (null != parent && !parent.exists()) {
290 parent.mkdirs();
291 }
292 if (!data.append) {
293 file.delete();
294 }
295
296 final OutputStream os = new DummyOutputStream();
297 RandomAccessFile raf = null;
298 try {
299 raf = new RandomAccessFile(name, "rw");
300 final long position = (data.append) ? raf.length() : 0;
301 raf.setLength(position + data.regionLength);
302 return new MemoryMappedFileManager(raf, name, os, data.force, position, data.regionLength,
303 data.advertiseURI, data.layout);
304 } catch (final Exception ex) {
305 LOGGER.error("MemoryMappedFileManager (" + name + ") " + ex);
306 Closer.closeSilently(raf);
307 }
308 return null;
309 }
310 }
311 }