Skip to content

Commit b77bf49

Browse files
authored
Merge pull request #4869 from volodya-lombrozo/4852-outofmemory-fix
fix(#4852): resolve OutOfMemoryError for large files in Cache.java
2 parents 9e9a2a9 + bdde463 commit b77bf49

2 files changed

Lines changed: 69 additions & 6 deletions

File tree

eo-maven-plugin/src/main/java/org/eolang/maven/Cache.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,18 @@ private Path hash(final Path tail) {
9898
* @return Base64-encoded SHA-256 hash
9999
* @throws NoSuchAlgorithmException If SHA-256 algorithm is not available
100100
* @throws IOException If an I/O error occurs reading the file
101-
* @todo #4846:30min OutOfMemoryError for large files in cache.
102-
* The sha method reads the entire file into memory using Files.readAllBytes(file) which
103-
* could cause OutOfMemoryError for large files. Consider using a streaming approach with
104-
* MessageDigest.update() in a loop to hash the file in chunks, similar to how it's typically
105-
* done for large file hashing operations.
106101
*/
107102
private static String sha(final Path file) throws NoSuchAlgorithmException, IOException {
108103
final MessageDigest digest = MessageDigest.getInstance("SHA-256");
109-
final byte[] hash = digest.digest(Files.readAllBytes(file));
104+
try (var stream = Files.newInputStream(file)) {
105+
final byte[] buffer = new byte[8192];
106+
int read = stream.read(buffer);
107+
while (read != -1) {
108+
digest.update(buffer, 0, read);
109+
read = stream.read(buffer);
110+
}
111+
}
112+
final byte[] hash = digest.digest();
110113
return Base64.getEncoder().encodeToString(hash);
111114
}
112115
}

eo-maven-plugin/src/test/java/org/eolang/maven/CacheTest.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.security.NoSuchAlgorithmException;
1616
import java.util.Base64;
1717
import java.util.concurrent.atomic.AtomicInteger;
18+
import java.util.stream.IntStream;
1819
import org.hamcrest.MatcherAssert;
1920
import org.hamcrest.Matchers;
2021
import org.junit.jupiter.api.Test;
@@ -158,4 +159,63 @@ void writesCorrectShaHash(@Mktmp final Path temp) throws IOException, NoSuchAlgo
158159
)
159160
);
160161
}
162+
163+
@Test
164+
void generatesCorrectHashForLargeFile(
165+
@Mktmp final Path temp
166+
) throws IOException, NoSuchAlgorithmException {
167+
final var cache = temp.resolve("cache");
168+
Files.createDirectories(cache);
169+
final var source = temp.resolve("large.txt");
170+
final int lines = 100_000;
171+
final StringBuilder builder = new StringBuilder(lines * 10);
172+
IntStream.range(0, lines).forEach(
173+
i -> builder.append("Line ").append(i).append('\n')
174+
);
175+
final String content = builder.toString();
176+
Files.writeString(source, content, StandardCharsets.UTF_8);
177+
final var target = temp.resolve("out.txt");
178+
final var tail = source.getFileName();
179+
new Cache(cache, p -> content).apply(source, target, tail);
180+
MatcherAssert.assertThat(
181+
"SHA-256 hash file has incorrect content for large file",
182+
Files.readString(
183+
cache.resolve(String.format("%s.sha256", tail)),
184+
StandardCharsets.UTF_8
185+
),
186+
Matchers.equalTo(
187+
Base64.getEncoder().encodeToString(
188+
MessageDigest.getInstance("SHA-256")
189+
.digest(content.getBytes(StandardCharsets.UTF_8))
190+
)
191+
)
192+
);
193+
}
194+
195+
@Test
196+
void generatesCorrectHashForTinyFile(
197+
@Mktmp final Path temp
198+
) throws IOException, NoSuchAlgorithmException {
199+
final var cache = temp.resolve("cache");
200+
Files.createDirectories(cache);
201+
final var source = temp.resolve("tiny.txt");
202+
final String content = "x";
203+
Files.writeString(source, content, StandardCharsets.UTF_8);
204+
final var target = temp.resolve("out.txt");
205+
final var tail = source.getFileName();
206+
new Cache(cache, p -> content).apply(source, target, tail);
207+
MatcherAssert.assertThat(
208+
"SHA-256 hash file has incorrect content for tiny file",
209+
Files.readString(
210+
cache.resolve(String.format("%s.sha256", tail)),
211+
StandardCharsets.UTF_8
212+
),
213+
Matchers.equalTo(
214+
Base64.getEncoder().encodeToString(
215+
MessageDigest.getInstance("SHA-256")
216+
.digest(content.getBytes(StandardCharsets.UTF_8))
217+
)
218+
)
219+
);
220+
}
161221
}

0 commit comments

Comments
 (0)