Skip to content

Commit

Permalink
Add test for re-request
Browse files Browse the repository at this point in the history
Fix queued message send timing.
  • Loading branch information
caalador committed Jan 3, 2025
1 parent 5759c98 commit 0846385
Show file tree
Hide file tree
Showing 10 changed files with 473 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ private void doSendInvocationsToServer() {
registry.getRequestResponseTracker().startRequest();
send(payload);
return;
} else if (hasQueuedMessages() && resendMessageTimer == null) {
if (!registry.getRequestResponseTracker().hasActiveRequest()) {
registry.getRequestResponseTracker().startRequest();
}
sendPayload(messageQueue.get(0));
return;
}

ServerRpcQueue serverRpcQueue = registry.getServerRpcQueue();
Expand Down Expand Up @@ -186,10 +192,6 @@ private JsonObject preparePayload(final JsonArray reqInvocations,
payload.put(ApplicationConstants.CSRF_TOKEN, csrfToken);
}
payload.put(ApplicationConstants.RPC_INVOCATIONS, reqInvocations);
payload.put(ApplicationConstants.SERVER_SYNC_ID,
registry.getMessageHandler().getLastSeenServerSyncId());
payload.put(ApplicationConstants.CLIENT_TO_SERVER_ID,
clientToServerMessageId++);
if (extraJson != null) {
for (String key : extraJson.keys()) {
JsonValue value = extraJson.get(key);
Expand All @@ -208,13 +210,9 @@ private JsonObject preparePayload(final JsonArray reqInvocations,
* The contents of the request to send
*/
public void send(final JsonObject payload) {
if (!registry.getRequestResponseTracker().hasActiveRequest()) {
// Someone called send directly as request not set active.
// If queue empty add message and wait to send.
if (!messageQueue.isEmpty()) {
messageQueue.add(payload);
return;
}
if (!messageQueue.isEmpty()) {
messageQueue.add(payload);
return;
}
messageQueue.add(payload);
sendPayload(payload);
Expand All @@ -228,6 +226,11 @@ public void send(final JsonObject payload) {
* The contents of the request to send
*/
private void sendPayload(final JsonObject payload) {
payload.put(ApplicationConstants.SERVER_SYNC_ID,
registry.getMessageHandler().getLastSeenServerSyncId());
payload.put(ApplicationConstants.CLIENT_TO_SERVER_ID,
clientToServerMessageId++);

if (push != null && push.isBidirectional()) {
// When using bidirectional transport, the payload is not resent
// to the server during reconnection attempts.
Expand All @@ -247,7 +250,9 @@ private void sendPayload(final JsonObject payload) {
resendMessageTimer = new Timer() {
@Override
public void run() {
resendMessageTimer = null;
resendMessageTimer
.schedule(registry.getApplicationConfiguration()
.getMaxMessageSuspendTimeout() + 500);
if (!registry.getRequestResponseTracker()
.hasActiveRequest()) {
registry.getRequestResponseTracker().startRequest();
Expand All @@ -264,7 +269,6 @@ private void resetTimer() {
if (resendMessageTimer != null) {
resendMessageTimer.cancel();
resendMessageTimer = null;

}
}

Expand Down Expand Up @@ -376,9 +380,6 @@ public void setClientToServerMessageId(int nextExpectedId, boolean force) {
+ 1 == nextExpectedId) {
resetTimer();
messageQueue.remove(0);
if (!messageQueue.isEmpty()) {
sendPayload(messageQueue.get(0));
}
}
}
}
Expand Down Expand Up @@ -410,6 +411,7 @@ public void setClientToServerMessageId(int nextExpectedId, boolean force) {
} else {
// Server has not yet seen all our messages
// Do nothing as they will arrive eventually
resetTimer();
}
}

Expand Down Expand Up @@ -445,4 +447,8 @@ void clearResynchronizationState() {
ResynchronizationState getResynchronizationState() {
return resynchronizationState;
}

public boolean hasQueuedMessages() {
return !messageQueue.isEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,10 @@ public void endRequest() {
hasActiveRequest = false;

if ((registry.getUILifecycle().isRunning()
&& registry.getServerRpcQueue().isFlushPending())
&& (registry.getServerRpcQueue().isFlushPending())
|| registry.getMessageSender()
.getResynchronizationState() == ResynchronizationState.SEND_TO_SERVER) {
.getResynchronizationState() == ResynchronizationState.SEND_TO_SERVER
|| registry.getMessageSender().hasQueuedMessages())) {
// Send the pending RPCs immediately.
// This might be an unnecessary optimization as ServerRpcQueue has a
// finally scheduled command which trigger the send if we do not do
Expand Down
1 change: 1 addition & 0 deletions flow-tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@
<module>test-react-adapter</module>
<module>test-react-adapter/pom-production.xml</module>
<module>test-legacy-frontend</module>
<module>test-client-queue</module>
</modules>
</profile>
<profile>
Expand Down
94 changes: 94 additions & 0 deletions flow-tests/test-client-queue/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>flow-tests</artifactId>
<groupId>com.vaadin</groupId>
<version>24.7-SNAPSHOT</version>
</parent>
<artifactId>flow-client-queue-test</artifactId>
<name>Test Flow client queue</name>

<packaging>war</packaging>

<dependencies>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>flow-test-resources</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-dev-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>flow-html-components-testbench</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<!-- Auto run clean to remove previous mode compilation -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<executions>
<execution>
<id>auto-clean</id>
<phase>initialize</phase>
<goals>
<goal>clean</goal>
</goals>
<!-- <configuration>-->
<!-- <filesets>-->
<!-- <fileset>-->
<!-- <directory>${project.basedir}</directory>-->
<!-- <includes>-->
<!-- <include>package*.json</include>-->
<!-- <include>types.d.ts</include>-->
<!-- <include>tsconfig.json</include>-->
<!-- <include>pnpm-lock.yaml</include>-->
<!-- <include>bun.lockb</include>-->
<!-- <include>node_modules/**</include>-->
<!-- </includes>-->
<!-- </fileset>-->
<!-- </filesets>-->
<!-- </configuration>-->
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<!-- Run flow plugin to build frontend -->
<plugin>
<groupId>com.vaadin</groupId>
<artifactId>flow-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>prepare-frontend</goal>
<!-- <goal>build-frontend</goal>-->
</goals>
</execution>
</executions>
<configuration>
<frontendHotdeploy>true</frontendHotdeploy>
</configuration>
</plugin>
<!-- Run jetty before integration tests, and stop after -->
<plugin>
<groupId>org.eclipse.jetty.ee10</groupId>
<artifactId>jetty-ee10-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright 2000-2025 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.flow.misc.ui;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import com.vaadin.flow.function.DeploymentConfiguration;
import com.vaadin.flow.i18n.TranslationFileRequestHandler;
import com.vaadin.flow.internal.DevModeHandler;
import com.vaadin.flow.internal.DevModeHandlerManager;
import com.vaadin.flow.server.Mode;
import com.vaadin.flow.server.RequestHandler;
import com.vaadin.flow.server.ServiceException;
import com.vaadin.flow.server.UnsupportedBrowserHandler;
import com.vaadin.flow.server.VaadinServlet;
import com.vaadin.flow.server.VaadinServletService;
import com.vaadin.flow.server.communication.FaviconHandler;
import com.vaadin.flow.server.communication.HeartbeatHandler;
import com.vaadin.flow.server.communication.IndexHtmlRequestHandler;
import com.vaadin.flow.server.communication.JavaScriptBootstrapHandler;
import com.vaadin.flow.server.communication.PushRequestHandler;
import com.vaadin.flow.server.communication.PwaHandler;
import com.vaadin.flow.server.communication.SessionRequestHandler;
import com.vaadin.flow.server.communication.StreamRequestHandler;
import com.vaadin.flow.server.communication.WebComponentBootstrapHandler;
import com.vaadin.flow.server.communication.WebComponentProvider;

public class CustomService extends VaadinServletService {

public CustomService(VaadinServlet servlet,
DeploymentConfiguration deploymentConfiguration) {
super(servlet, deploymentConfiguration);
}

@Override
protected List<RequestHandler> createRequestHandlers()
throws ServiceException {
List<RequestHandler> handlers = new ArrayList<>();
handlers.add(new FaviconHandler());
handlers.add(new JavaScriptBootstrapHandler());
handlers.add(new SessionRequestHandler());
handlers.add(new HeartbeatHandler());

handlers.add(new CustomUidlRequestHandler());

handlers.add(new UnsupportedBrowserHandler());

handlers.add(new StreamRequestHandler());

handlers.add(new PwaHandler(() -> getPwaRegistry()));

handlers.add(new TranslationFileRequestHandler(
getInstantiator().getI18NProvider()));

handlers.add(new WebComponentBootstrapHandler());
handlers.add(new WebComponentProvider());

Mode mode = getDeploymentConfiguration().getMode();
if (mode == Mode.DEVELOPMENT_FRONTEND_LIVERELOAD
|| mode == Mode.DEVELOPMENT_BUNDLE) {
Optional<DevModeHandler> handlerManager = DevModeHandlerManager
.getDevModeHandler(this);
if (handlerManager.isPresent()) {
DevModeHandler devModeHandler = handlerManager.get();
// WebComponentProvider handler should run before DevModeHandler
// to avoid responding with html contents when dev bundle is
// not ready (e.g. dev-mode-not-ready.html)
handlers.stream().filter(WebComponentProvider.class::isInstance)
.findFirst().map(handlers::indexOf)
.ifPresentOrElse(idx -> {
handlers.add(idx, devModeHandler);
}, () -> handlers.add(devModeHandler));
}
}

// PushRequestHandler should run before DevModeHandler to avoid
// responding with html contents when dev mode server is not ready
// (e.g. dev-mode-not-ready.html)
if (isAtmosphereAvailable()) {
try {
handlers.add(new PushRequestHandler(this));
} catch (ServiceException e) {
// Atmosphere init failed. Push won't work but we don't throw a
// service exception as we don't want to prevent non-push
// applications from working
}
}

handlers.add(0, new IndexHtmlRequestHandler());
return handlers;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2000-2024 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.flow.misc.ui;

import jakarta.servlet.annotation.WebServlet;

import com.vaadin.flow.function.DeploymentConfiguration;
import com.vaadin.flow.server.ServiceException;
import com.vaadin.flow.server.VaadinServlet;
import com.vaadin.flow.server.VaadinServletService;

@WebServlet(urlPatterns = "/*", asyncSupported = true)
public class CustomServlet extends VaadinServlet {

@Override
protected VaadinServletService createServletService(
DeploymentConfiguration deploymentConfiguration)
throws ServiceException {
CustomService service = new CustomService(this,
deploymentConfiguration);
service.init();
return service;
}
}
Loading

0 comments on commit 0846385

Please sign in to comment.