Skip to content

Commit 379a698

Browse files
authored
Merge pull request #1026 from rubenporras/nonBlockingm
perf: add a NonLockingBufferInputStream for DirectLinkingResource
2 parents 263b9fe + 87a93a7 commit 379a698

File tree

2 files changed

+291
-4
lines changed

2 files changed

+291
-4
lines changed

com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/persistence/DirectLinkingResourceStorageLoadable.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
package com.avaloq.tools.ddk.xtext.resource.persistence;
1313

14-
import java.io.BufferedInputStream;
1514
import java.io.DataInputStream;
1615
import java.io.IOException;
1716
import java.io.InputStream;
@@ -150,7 +149,7 @@ protected void loadEntries(final StorageAwareResource resource, final ZipInputSt
150149
// fall through
151150
case LOAD:
152151
positioner.position(Constituent.CONTENT);
153-
readContents(resource, new BufferedInputStream(zipIn));
152+
readContents(resource, new NonLockingBufferInputStream(zipIn));
154153
break;
155154
}
156155

@@ -163,7 +162,7 @@ protected void loadEntries(final StorageAwareResource resource, final ZipInputSt
163162
break;
164163
default:
165164
positioner.position(Constituent.ASSOCIATIONS);
166-
readAssociationsAdapter(resource, new BufferedInputStream(zipIn));
165+
readAssociationsAdapter(resource, new NonLockingBufferInputStream(zipIn));
167166
break;
168167
}
169168

@@ -194,7 +193,7 @@ protected void loadEntries(final StorageAwareResource resource, final ZipInputSt
194193
break;
195194
case LOAD:
196195
positioner.position(Constituent.NODE_MODEL);
197-
readNodeModel(resource, new BufferedInputStream(zipIn), content);
196+
readNodeModel(resource, new NonLockingBufferInputStream(zipIn), content);
198197
break;
199198
}
200199
}
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 Avaloq Group AG and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Avaloq Group AG - initial API and implementation
10+
*******************************************************************************/
11+
package com.avaloq.tools.ddk.xtext.resource.persistence;
12+
13+
import java.io.FilterInputStream;
14+
import java.io.IOException;
15+
import java.io.InputStream;
16+
import java.io.OutputStream;
17+
import java.util.Arrays;
18+
import java.util.Objects;
19+
20+
21+
/**
22+
* Like {@code BufferedInputStream} without supporting concurrency.
23+
*/
24+
@SuppressWarnings("nls")
25+
public class NonLockingBufferInputStream extends FilterInputStream {
26+
27+
private static final int DEFAULT_BUFFER_SIZE = 8192;
28+
private static final byte[] EMPTY = new byte[0];
29+
private final int initialSize;
30+
31+
private byte[] buf;
32+
private int count;
33+
private int pos;
34+
private int markpos = -1;
35+
private int marklimit;
36+
37+
private byte[] getBufIfOpen(final boolean allocateIfEmpty) throws IOException {
38+
if (allocateIfEmpty && buf == EMPTY) {
39+
buf = new byte[initialSize];
40+
}
41+
return buf;
42+
}
43+
44+
private byte[] getBufIfOpen() throws IOException {
45+
return getBufIfOpen(true);
46+
}
47+
48+
private void ensureOpen() throws IOException {
49+
if (buf == null) {
50+
throw new IOException("Stream closed");
51+
}
52+
}
53+
54+
/**
55+
* Creates a {@code NonLockingBufferInputStream}.
56+
*
57+
* @param in
58+
* the underlying input stream.
59+
*/
60+
public NonLockingBufferInputStream(final InputStream in) {
61+
this(in, DEFAULT_BUFFER_SIZE);
62+
}
63+
64+
/**
65+
* Creates a {@code NonLockingBufferInputStream}
66+
* with the specified buffer size.
67+
*
68+
* @param in
69+
* the underlying input stream.
70+
* @param size
71+
* the buffer size.
72+
* @throws IllegalArgumentException
73+
* if {@code size <= 0}.
74+
*/
75+
public NonLockingBufferInputStream(final InputStream in, final int size) {
76+
super(in);
77+
if (size <= 0) {
78+
throw new IllegalArgumentException("Buffer size <= 0");
79+
}
80+
initialSize = size;
81+
buf = new byte[size];
82+
}
83+
84+
private static final int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;
85+
86+
private static int newLength(final int oldLength, final int minGrowth, final int prefGrowth) {
87+
int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow
88+
if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
89+
return prefLength;
90+
} else {
91+
return hugeLength(oldLength, minGrowth);
92+
}
93+
}
94+
95+
private static int hugeLength(final int oldLength, final int minGrowth) {
96+
int minLength = oldLength + minGrowth;
97+
if (minLength < 0) { // overflow
98+
throw new OutOfMemoryError("Required array length " + oldLength + " + " + minGrowth + " is too large");
99+
} else if (minLength <= SOFT_MAX_ARRAY_LENGTH) {
100+
return SOFT_MAX_ARRAY_LENGTH;
101+
} else {
102+
return minLength;
103+
}
104+
}
105+
106+
private void fill() throws IOException {
107+
byte[] buffer = getBufIfOpen();
108+
if (markpos == -1) {
109+
pos = 0; /* no mark: throw away the buffer */
110+
} else if (pos >= buffer.length) { /* no room left in buffer */
111+
if (markpos > 0) { /* can throw away early part of the buffer */
112+
int sz = pos - markpos;
113+
System.arraycopy(buffer, markpos, buffer, 0, sz);
114+
pos = sz;
115+
markpos = 0;
116+
} else if (buffer.length >= marklimit) {
117+
markpos = -1; /* buffer got too big, invalidate mark */
118+
pos = 0; /* drop buffer contents */
119+
} else { /* grow buffer */
120+
int nsz = newLength(pos, 1, pos);
121+
if (nsz > marklimit) {
122+
nsz = marklimit;
123+
}
124+
byte[] nbuf = new byte[nsz];
125+
System.arraycopy(buffer, 0, nbuf, 0, pos);
126+
buffer = nbuf;
127+
}
128+
}
129+
count = pos;
130+
int n = in.read(buffer, pos, buffer.length - pos);
131+
if (n > 0) {
132+
count = n + pos;
133+
}
134+
}
135+
136+
@Override
137+
@SuppressWarnings("checkstyle:MagicNumberCheck")
138+
public int read() throws IOException {
139+
if (pos >= count) {
140+
fill();
141+
if (pos >= count) {
142+
return -1;
143+
}
144+
}
145+
return getBufIfOpen()[pos++] & 0xff;
146+
}
147+
148+
private int read1(final byte[] b, final int off, final int len) throws IOException {
149+
int avail = count - pos;
150+
if (avail <= 0) {
151+
/*
152+
* If the requested length is at least as large as the buffer, and
153+
* if there is no mark/reset activity, do not bother to copy the
154+
* bytes into the local buffer. In this way buffered streams will
155+
* cascade harmlessly.
156+
*/
157+
int size = Math.max(getBufIfOpen(false).length, initialSize);
158+
if (len >= size && markpos == -1) {
159+
return in.read(b, off, len);
160+
}
161+
fill();
162+
avail = count - pos;
163+
if (avail <= 0) {
164+
return -1;
165+
}
166+
}
167+
int cnt = (avail < len) ? avail : len;
168+
System.arraycopy(getBufIfOpen(), pos, b, off, cnt);
169+
pos += cnt;
170+
return cnt;
171+
}
172+
173+
@Override
174+
public int read(final byte[] b, final int off, final int len) throws IOException {
175+
ensureOpen();
176+
if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
177+
throw new IndexOutOfBoundsException();
178+
} else if (len == 0) {
179+
return 0;
180+
}
181+
182+
int n = 0;
183+
for (;;) {
184+
int nread = read1(b, off + n, len - n);
185+
if (nread <= 0) {
186+
return (n == 0) ? nread : n;
187+
}
188+
n += nread;
189+
if (n >= len) {
190+
return n;
191+
}
192+
// if not closed but no bytes available, return
193+
InputStream input = in;
194+
if (input != null && input.available() <= 0) {
195+
return n;
196+
}
197+
}
198+
}
199+
200+
@Override
201+
public long skip(final long n) throws IOException {
202+
ensureOpen();
203+
if (n <= 0) {
204+
return 0;
205+
}
206+
long avail = count - pos;
207+
208+
if (avail <= 0) {
209+
// If no mark position set then don't keep in buffer
210+
if (markpos == -1) {
211+
return in.skip(n);
212+
}
213+
214+
// Fill in buffer to save bytes for reset
215+
fill();
216+
avail = count - pos;
217+
if (avail <= 0) {
218+
return 0;
219+
}
220+
}
221+
222+
long skipped = (avail < n) ? avail : n;
223+
pos += (int) skipped;
224+
return skipped;
225+
}
226+
227+
@Override
228+
public int available() throws IOException {
229+
int n = count - pos;
230+
int avail = in.available();
231+
return n > (Integer.MAX_VALUE - avail) ? Integer.MAX_VALUE : n + avail;
232+
}
233+
234+
@Override
235+
public void mark(final int readlimit) {
236+
marklimit = readlimit;
237+
markpos = pos;
238+
}
239+
240+
@Override
241+
public void reset() throws IOException {
242+
ensureOpen();
243+
if (markpos < 0) {
244+
throw new IOException("Resetting to invalid mark");
245+
}
246+
pos = markpos;
247+
}
248+
249+
@Override
250+
public boolean markSupported() {
251+
return true;
252+
}
253+
254+
@Override
255+
public void close() throws IOException {
256+
while (buf != null) {
257+
buf = null;
258+
InputStream input = in;
259+
in = null;
260+
if (input != null) {
261+
input.close();
262+
}
263+
return;
264+
}
265+
}
266+
267+
@Override
268+
public long transferTo(final OutputStream out) throws IOException {
269+
Objects.requireNonNull(out, "out");
270+
if (markpos == -1) {
271+
int avail = count - pos;
272+
if (avail > 0) {
273+
// Prevent poisoning and leaking of buf
274+
byte[] buffer = Arrays.copyOfRange(getBufIfOpen(), pos, count);
275+
out.write(buffer);
276+
pos = count;
277+
}
278+
try {
279+
return Math.addExact(avail, in.transferTo(out));
280+
} catch (ArithmeticException ignore) {
281+
return Long.MAX_VALUE;
282+
}
283+
} else {
284+
return super.transferTo(out);
285+
}
286+
}
287+
288+
}

0 commit comments

Comments
 (0)