All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.tomcat.websocket.server.WsRemoteEndpointImplServer Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.tomcat.websocket.server;

import java.io.EOFException;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

import jakarta.servlet.http.WebConnection;
import jakarta.websocket.SendHandler;
import jakarta.websocket.SendResult;

import org.apache.coyote.http11.upgrade.UpgradeInfo;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.net.SocketWrapperBase;
import org.apache.tomcat.util.net.SocketWrapperBase.BlockingMode;
import org.apache.tomcat.util.res.StringManager;
import org.apache.tomcat.websocket.Constants;
import org.apache.tomcat.websocket.Transformation;
import org.apache.tomcat.websocket.WsRemoteEndpointImplBase;
import org.apache.tomcat.websocket.WsSession;

/**
 * This is the server side {@link jakarta.websocket.RemoteEndpoint} implementation - i.e. what the server uses to send
 * data to the client.
 */
public class WsRemoteEndpointImplServer extends WsRemoteEndpointImplBase {

    private static final StringManager sm = StringManager.getManager(WsRemoteEndpointImplServer.class);
    private final Log log = LogFactory.getLog(WsRemoteEndpointImplServer.class); // must not be static

    private final SocketWrapperBase socketWrapper;
    private final UpgradeInfo upgradeInfo;
    private final WebConnection connection;
    private final WsWriteTimeout wsWriteTimeout;
    private volatile SendHandler handler = null;
    private volatile ByteBuffer[] buffers = null;

    private volatile long timeoutExpiry = -1;

    public WsRemoteEndpointImplServer(SocketWrapperBase socketWrapper, UpgradeInfo upgradeInfo,
            WsServerContainer serverContainer, WebConnection connection) {
        this.socketWrapper = socketWrapper;
        this.upgradeInfo = upgradeInfo;
        this.connection = connection;
        this.wsWriteTimeout = serverContainer.getTimeout();
    }


    @Override
    protected final boolean isMasked() {
        return false;
    }


    /**
     * {@inheritDoc}
     * 

* The close message is a special case. It needs to be blocking else implementing the clean-up that follows the * sending of the close message gets a lot more complicated. On the server, this creates additional complications as * a dead-lock may occur in the following scenario: *

    *
  1. Application thread writes message using non-blocking
  2. *
  3. Write does not complete (write logic holds message pending lock)
  4. *
  5. Socket is added to poller (or equivalent) for write *
  6. Client sends close message
  7. *
  8. Container processes received close message and tries to send close message in response
  9. *
  10. Container holds socket lock and is blocked waiting for message pending lock
  11. *
  12. Poller fires write possible event for socket
  13. *
  14. Container tries to process write possible event but is blocked waiting for socket lock
  15. *
  16. Processing of the WebSocket connection is dead-locked until the original message write times out
  17. *
* The purpose of this method is to break the above dead-lock. It does this by returning control of the processor to * the socket wrapper and releasing the socket lock while waiting for the pending message write to complete. * Normally, that would be a terrible idea as it creates the possibility that the processor is returned to the pool * more than once under various error conditions. In this instance it is safe because these are upgrade processors * (isUpgrade() returns {@code true}) and upgrade processors are never pooled. *

* TODO: Despite the complications it creates, it would be worth exploring the possibility of processing a received * close frame in a non-blocking manner. */ @Override protected boolean acquireMessagePartInProgressSemaphore(byte opCode, long timeoutExpiry) throws InterruptedException { /* * Special handling is required only when all of the following are true: * - A close message is being sent * - This thread currently holds the socketWrapper lock (i.e. the thread is current processing a socket event) */ if (!(opCode == Constants.OPCODE_CLOSE && socketWrapper.getLock().isHeldByCurrentThread())) { // Skip special handling return super.acquireMessagePartInProgressSemaphore(opCode, timeoutExpiry); } int socketWrapperLockCount = socketWrapper.getLock().getHoldCount(); while (!messagePartInProgress.tryAcquire()) { if (timeoutExpiry < System.currentTimeMillis()) { return false; } try { // Release control of the processor socketWrapper.setCurrentProcessor(connection); // Release the per socket lock(s) for (int i = 0; i < socketWrapperLockCount; i++) { socketWrapper.getLock().unlock(); } // Provide opportunity for another thread to obtain the socketWrapper lock Thread.yield(); } finally { // Re-obtain the per socket lock(s) for (int i = 0; i < socketWrapperLockCount; i++) { socketWrapper.getLock().lock(); } // Re-take control of the processor socketWrapper.takeCurrentProcessor(); } } return true; } @Override protected void doWrite(SendHandler handler, long blockingWriteTimeoutExpiry, ByteBuffer... buffers) { if (socketWrapper.hasAsyncIO()) { final boolean block = (blockingWriteTimeoutExpiry != -1); long timeout = -1; if (block) { timeout = blockingWriteTimeoutExpiry - System.currentTimeMillis(); if (timeout <= 0) { SendResult sr = new SendResult(getSession(), new SocketTimeoutException()); handler.onResult(sr); return; } } else { this.handler = handler; timeout = getSendTimeout(); if (timeout > 0) { // Register with timeout thread timeoutExpiry = timeout + System.currentTimeMillis(); wsWriteTimeout.register(this); } } socketWrapper.write(block ? BlockingMode.BLOCK : BlockingMode.SEMI_BLOCK, timeout, TimeUnit.MILLISECONDS, null, SocketWrapperBase.COMPLETE_WRITE_WITH_COMPLETION, new CompletionHandler() { @Override public void completed(Long result, Void attachment) { if (block) { long timeout = blockingWriteTimeoutExpiry - System.currentTimeMillis(); if (timeout <= 0) { failed(new SocketTimeoutException(), null); } else { handler.onResult(new SendResult(getSession())); } } else { wsWriteTimeout.unregister(WsRemoteEndpointImplServer.this); clearHandler(null, true); } } @Override public void failed(Throwable exc, Void attachment) { if (block) { SendResult sr = new SendResult(getSession(), exc); handler.onResult(sr); } else { wsWriteTimeout.unregister(WsRemoteEndpointImplServer.this); clearHandler(exc, true); close(); } } }, buffers); } else { if (blockingWriteTimeoutExpiry == -1) { this.handler = handler; this.buffers = buffers; // This is definitely the same thread that triggered the write so a // dispatch will be required. onWritePossible(true); } else { // Blocking try { for (ByteBuffer buffer : buffers) { long timeout = blockingWriteTimeoutExpiry - System.currentTimeMillis(); if (timeout <= 0) { SendResult sr = new SendResult(getSession(), new SocketTimeoutException()); handler.onResult(sr); return; } socketWrapper.setWriteTimeout(timeout); socketWrapper.write(true, buffer); } long timeout = blockingWriteTimeoutExpiry - System.currentTimeMillis(); if (timeout <= 0) { SendResult sr = new SendResult(getSession(), new SocketTimeoutException()); handler.onResult(sr); return; } socketWrapper.setWriteTimeout(timeout); socketWrapper.flush(true); handler.onResult(new SendResult(getSession())); } catch (IOException e) { SendResult sr = new SendResult(getSession(), e); handler.onResult(sr); } } } } @Override protected void updateStats(long payloadLength) { upgradeInfo.addMsgsSent(1); upgradeInfo.addBytesSent(payloadLength); } public void onWritePossible(boolean useDispatch) { // Note: Unused for async IO ByteBuffer[] buffers = this.buffers; if (buffers == null) { // Servlet 3.1 will call the write listener once even if nothing // was written return; } boolean complete = false; try { socketWrapper.flush(false); // If this is false there will be a call back when it is true while (socketWrapper.isReadyForWrite()) { complete = true; for (ByteBuffer buffer : buffers) { if (buffer.hasRemaining()) { complete = false; socketWrapper.write(false, buffer); break; } } if (complete) { socketWrapper.flush(false); complete = socketWrapper.isReadyForWrite(); if (complete) { wsWriteTimeout.unregister(this); clearHandler(null, useDispatch); } break; } } } catch (IOException | IllegalStateException e) { wsWriteTimeout.unregister(this); clearHandler(e, useDispatch); close(); } if (!complete) { // Async write is in progress long timeout = getSendTimeout(); if (timeout > 0) { // Register with timeout thread timeoutExpiry = timeout + System.currentTimeMillis(); wsWriteTimeout.register(this); } } } @Override protected void doClose() { if (handler != null) { // close() can be triggered by a wide range of scenarios. It is far // simpler just to always use a dispatch than it is to try and track // whether or not this method was called by the same thread that // triggered the write clearHandler(new EOFException(), true); } try { socketWrapper.close(); } catch (Exception e) { if (log.isInfoEnabled()) { log.info(sm.getString("wsRemoteEndpointServer.closeFailed"), e); } } wsWriteTimeout.unregister(this); } protected long getTimeoutExpiry() { return timeoutExpiry; } /* * Currently this is only called from the background thread so we could just call clearHandler() with useDispatch == * false but the method parameter was added in case other callers started to use this method to make sure that those * callers think through what the correct value of useDispatch is for them. */ protected void onTimeout(boolean useDispatch) { if (handler != null) { clearHandler(new SocketTimeoutException(), useDispatch); } close(); } @Override protected void setTransformation(Transformation transformation) { // Overridden purely so it is visible to other classes in this package super.setTransformation(transformation); } /** * @param t The throwable associated with any error that occurred * @param useDispatch Should {@link SendHandler#onResult(SendResult)} be called from a new thread, keeping in mind * the requirements of {@link jakarta.websocket.RemoteEndpoint.Async} */ void clearHandler(Throwable t, boolean useDispatch) { // Setting the result marks this (partial) message as // complete which means the next one may be sent which // could update the value of the handler. Therefore, keep a // local copy before signalling the end of the (partial) // message. SendHandler sh = handler; handler = null; buffers = null; if (sh != null) { if (useDispatch) { OnResultRunnable r = new OnResultRunnable(getSession(), sh, t); try { socketWrapper.execute(r); } catch (RejectedExecutionException ree) { // Can't use the executor so call the runnable directly. // This may not be strictly specification compliant in all // cases but during shutdown only close messages are going // to be sent so there should not be the issue of nested // calls leading to stack overflow as described in bug // 55715. The issues with nested calls was the reason for // the separate thread requirement in the specification. r.run(); } } else { if (t == null) { sh.onResult(new SendResult(getSession())); } else { sh.onResult(new SendResult(getSession(), t)); } } } } @Override protected ReentrantLock getLock() { return socketWrapper.getLock(); } private static class OnResultRunnable implements Runnable { private final WsSession session; private final SendHandler sh; private final Throwable t; private OnResultRunnable(WsSession session, SendHandler sh, Throwable t) { this.session = session; this.sh = sh; this.t = t; } @Override public void run() { if (t == null) { sh.onResult(new SendResult(session)); } else { sh.onResult(new SendResult(session, t)); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy