Skip to content

Commit 44c8f07

Browse files
authored
fix: interrupt all uploads (CP: 23.6) (#7619)
1 parent b472a0d commit 44c8f07

File tree

5 files changed

+178
-17
lines changed

5 files changed

+178
-17
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2000-2025 Vaadin Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.vaadin.flow.component.upload.tests;
17+
18+
import java.io.FilterOutputStream;
19+
import java.io.IOException;
20+
import java.io.OutputStream;
21+
22+
import com.vaadin.flow.component.html.Div;
23+
import com.vaadin.flow.component.upload.Upload;
24+
import com.vaadin.flow.component.upload.receivers.MultiFileMemoryBuffer;
25+
import com.vaadin.flow.router.Route;
26+
27+
@Route("vaadin-upload/interrupt")
28+
public class UploadInterruptView extends Div {
29+
30+
public UploadInterruptView() {
31+
Div output = new Div();
32+
output.setId("test-output");
33+
Div eventsOutput = new Div();
34+
eventsOutput.setId("test-events-output");
35+
36+
MultiFileMemoryBuffer buffer = new SlowMultiFileMemoryBuffer();
37+
Upload upload = new Upload(buffer);
38+
upload.setAcceptedFileTypes(".txt");
39+
upload.addStartedListener(event -> {
40+
if (isInterruptableFile(event.getFileName())) {
41+
event.getUpload().interruptUpload();
42+
}
43+
});
44+
upload.addFailedListener(event -> {
45+
eventsOutput.add("-failed");
46+
output.add("FAILED:" + event.getFileName() + ","
47+
+ event.getReason().getMessage());
48+
});
49+
upload.addSucceededListener(event -> eventsOutput.add("-succeeded"));
50+
upload.addAllFinishedListener(event -> eventsOutput.add("-finished"));
51+
52+
add(upload, output, eventsOutput);
53+
}
54+
55+
private static boolean isInterruptableFile(String fileName) {
56+
return fileName != null && fileName.endsWith(".interrupt.txt");
57+
}
58+
59+
// Returns an OutputStream that delays write operations for uploads. The
60+
// delay ensures that the interruption flag is set before uploads completion
61+
// so that the test can verify all uploads failed.
62+
private static class SlowMultiFileMemoryBuffer
63+
extends MultiFileMemoryBuffer {
64+
@Override
65+
public OutputStream receiveUpload(String fileName, String MIMEType) {
66+
OutputStream outputStream = super.receiveUpload(fileName, MIMEType);
67+
if (isInterruptableFile(fileName)) {
68+
// Also delay the interrupted file to allow other uploads to
69+
// start
70+
return new SlowOutputStream(outputStream, 500);
71+
}
72+
{
73+
return new SlowOutputStream(outputStream, 1000);
74+
}
75+
}
76+
}
77+
78+
private static class SlowOutputStream extends FilterOutputStream {
79+
80+
private final int delay;
81+
82+
SlowOutputStream(OutputStream delegate, int delay) {
83+
super(delegate);
84+
this.delay = delay;
85+
}
86+
87+
@Override
88+
public void write(byte[] b, int off, int len) throws IOException {
89+
try {
90+
Thread.sleep(delay);
91+
} catch (InterruptedException e) {
92+
Thread.currentThread().interrupt();
93+
throw new IOException("Interrupted", e);
94+
}
95+
super.write(b, off, len);
96+
}
97+
}
98+
}

vaadin-upload-flow-parent/vaadin-upload-flow-integration-tests/src/test/java/com/vaadin/flow/component/upload/tests/AbstractUploadIT.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,15 @@ public abstract class AbstractUploadIT extends AbstractComponentIT {
2828
* @throws IOException
2929
*/
3030
File createTempFile() throws IOException {
31-
File tempFile = File.createTempFile("TestFileUpload", ".txt");
31+
return createTempFile("txt");
32+
}
33+
34+
/**
35+
* @return The generated temp file handle with the provided extension
36+
* @throws IOException
37+
*/
38+
File createTempFile(String extension) throws IOException {
39+
File tempFile = File.createTempFile("TestFileUpload", "." + extension);
3240
BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile));
3341
writer.write(getTempFileContents());
3442
writer.close();
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2000-2025 Vaadin Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.vaadin.flow.component.upload.tests;
17+
18+
import java.io.File;
19+
import java.util.List;
20+
21+
import org.junit.Assert;
22+
import org.junit.Before;
23+
import org.junit.Test;
24+
import org.openqa.selenium.WebElement;
25+
26+
import com.vaadin.flow.component.upload.testbench.UploadElement;
27+
import com.vaadin.flow.testutil.TestPath;
28+
29+
@TestPath("vaadin-upload/interrupt")
30+
public class UploadInterruptIT extends AbstractUploadIT {
31+
32+
private UploadElement upload;
33+
private WebElement uploadOutput;
34+
private WebElement eventsOutput;
35+
36+
@Before
37+
public void init() {
38+
open();
39+
upload = $(UploadElement.class).waitForFirst();
40+
uploadOutput = $("div").id("test-output");
41+
eventsOutput = $("div").id("test-events-output");
42+
}
43+
44+
@Test
45+
public void uploadMultipleFiles_interruptUpload_allOngoingUploadsInterrupted()
46+
throws Exception {
47+
48+
List<File> files = List.of(createTempFile("txt"), createTempFile("txt"),
49+
createTempFile("interrupt.txt"), createTempFile("txt"),
50+
createTempFile("txt"));
51+
52+
upload.uploadMultiple(files, 10);
53+
54+
Assert.assertEquals("Expected all uploads to be interrupted",
55+
"-failed-failed-failed-failed-failed-finished",
56+
eventsOutput.getText());
57+
58+
files.forEach(file -> Assert.assertTrue(
59+
"Expected upload of " + file.getName() + " to be failed",
60+
uploadOutput.getText().contains(
61+
"FAILED:" + file.getName() + ",Upload interrupted")));
62+
}
63+
}

vaadin-upload-flow-parent/vaadin-upload-flow/src/main/java/com/vaadin/flow/component/upload/Upload.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
public class Upload extends GeneratedVaadinUpload<Upload> implements HasSize {
5858

5959
private StreamVariable streamVariable;
60-
private boolean interrupted = false;
60+
private volatile boolean interrupted = false;
6161

6262
private int activeUploads = 0;
6363
private boolean uploading;
@@ -389,7 +389,7 @@ private void startUpload() {
389389
* The interruption will be done by the receiving thread so this method will
390390
* return immediately and the actual interrupt will happen a bit later.
391391
* <p>
392-
* Note! this will interrupt all uploads in multi-upload mode.
392+
* Note! this will interrupt all ongoing uploads in multi-upload mode.
393393
*/
394394
public void interruptUpload() {
395395
if (isUploading()) {
@@ -399,7 +399,9 @@ public void interruptUpload() {
399399

400400
private void endUpload() {
401401
activeUploads--;
402-
interrupted = false;
402+
if (activeUploads == 0) {
403+
interrupted = false;
404+
}
403405
}
404406

405407
/**

vaadin-upload-flow-parent/vaadin-upload-testbench/src/main/java/com/vaadin/flow/component/upload/testbench/UploadElement.java

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,9 @@
1616
package com.vaadin.flow.component.upload.testbench;
1717

1818
import java.io.File;
19-
import java.time.Duration;
20-
import java.time.temporal.ChronoUnit;
2119
import java.util.List;
2220

2321
import org.openqa.selenium.WebDriver;
24-
import org.openqa.selenium.WebDriver.Timeouts;
2522
import org.openqa.selenium.WebElement;
2623
import org.openqa.selenium.WrapsDriver;
2724
import org.openqa.selenium.WrapsElement;
@@ -135,17 +132,10 @@ public void uploadMultiple(List<File> files, int maxSeconds) {
135132
* the number of seconds to wait for the upload to finish
136133
*/
137134
private void waitForUploads(int maxSeconds) {
138-
Timeouts timeouts = getDriver().manage().timeouts();
139-
timeouts.scriptTimeout(Duration.of(15, ChronoUnit.SECONDS));
140-
141-
String script = "var callback = arguments[arguments.length - 1];"
142-
+ "var upload = arguments[0];"
143-
+ "window.setTimeout(function() {"
144-
+ " var inProgress = upload.files.filter(function(file) { return file.uploading;}).length >0;"
145-
+ " if (!inProgress) callback();" //
146-
+ "}, 500);";
147-
getCommandExecutor().getDriver().executeAsyncScript(script, this);
135+
String script = "return arguments[0].files.every((file) => !file.uploading);";
148136

137+
waitUntil(driver -> (Boolean) executeScript(script, UploadElement.this),
138+
maxSeconds);
149139
}
150140

151141
private void removeFile(int i) {

0 commit comments

Comments
 (0)