Search in sources :

Example 1 with SelectionKeyAttachment

use of org.jivesoftware.smack.SmackReactor.SelectionKeyAttachment in project Smack by igniterealtime.

the class XmppTcpTransportModule method afterOutgoingElementsQueueModified.

private void afterOutgoingElementsQueueModified() {
    final SelectionKeyAttachment selectionKeyAttachment = this.selectionKeyAttachment;
    if (selectionKeyAttachment != null && selectionKeyAttachment.isReactorThreadRacing()) {
        // A reactor thread is already racing to the channel selected callback and will take care of this.
        reactorThreadAlreadyRacing.incrementAndGet();
        return;
    }
    afterOutgoingElementsQueueModifiedSetInterestOps.incrementAndGet();
    // Add OP_WRITE to the interested Ops, since we have now new things to write. Note that this may cause
    // multiple reactor threads to race to the channel selected callback in case we perform this right after
    // a select() returned with this selection key in the selected-key set. Hence we use tryLock() in the
    // channel selected callback to keep the invariant that only exactly one thread is performing the
    // callback.
    // Note that we need to perform setInterestedOps() *without* holding the channelSelectedCallbackLock, as
    // otherwise the reactor thread racing to the channel selected callback may found the lock still locked, which
    // would result in the outgoingElementsQueue not being handled.
    connectionInternal.setInterestOps(selectionKey, SelectionKey.OP_WRITE | SelectionKey.OP_READ);
}
Also used : SelectionKeyAttachment(org.jivesoftware.smack.SmackReactor.SelectionKeyAttachment)

Example 2 with SelectionKeyAttachment

use of org.jivesoftware.smack.SmackReactor.SelectionKeyAttachment in project Smack by igniterealtime.

the class XmppTcpTransportModule method onChannelSelected.

private void onChannelSelected(SelectableChannel selectedChannel, SelectionKey selectedSelectionKey) {
    assert selectionKey == null || selectionKey == selectedSelectionKey;
    SocketChannel selectedSocketChannel = (SocketChannel) selectedChannel;
    // We are *always* interested in OP_READ.
    int newInterestedOps = SelectionKey.OP_READ;
    boolean newPendingOutputFilterData = false;
    if (!channelSelectedCallbackLock.tryLock()) {
        rejectedChannelSelectedCallbacks.incrementAndGet();
        return;
    }
    handledChannelSelectedCallbacks++;
    long callbackBytesRead = 0;
    long callbackBytesWritten = 0;
    try {
        boolean destinationAddressChanged = false;
        boolean isLastPartOfElement = false;
        TopLevelStreamElement currentlyOutgonigTopLevelStreamElement = null;
        StringBuilder outgoingStreamForDebugger = null;
        writeLoop: while (true) {
            final boolean moreDataAvailable = !isLastPartOfElement || !connectionInternal.outgoingElementsQueue.isEmpty();
            if (filteredOutgoingBuffer != null || !networkOutgoingBuffers.isEmpty()) {
                if (filteredOutgoingBuffer != null) {
                    networkOutgoingBuffers.add(filteredOutgoingBuffer);
                    networkOutgoingBuffersBytes += filteredOutgoingBuffer.remaining();
                    filteredOutgoingBuffer = null;
                    if (moreDataAvailable && networkOutgoingBuffersBytes < 8096) {
                        continue;
                    }
                }
                ByteBuffer[] output = networkOutgoingBuffers.toArray(new ByteBuffer[networkOutgoingBuffers.size()]);
                long bytesWritten;
                try {
                    bytesWritten = selectedSocketChannel.write(output);
                } catch (IOException e) {
                    // We have seen here so far
                    // - IOException "Broken pipe"
                    handleReadWriteIoException(e);
                    break;
                }
                if (bytesWritten == 0) {
                    newInterestedOps |= SelectionKey.OP_WRITE;
                    break;
                }
                callbackBytesWritten += bytesWritten;
                networkOutgoingBuffersBytes -= bytesWritten;
                List<? extends Buffer> prunedBuffers = pruneBufferList(networkOutgoingBuffers);
                for (Buffer prunedBuffer : prunedBuffers) {
                    List<TopLevelStreamElement> sendElements = bufferToElementMap.remove(prunedBuffer);
                    if (sendElements == null) {
                        continue;
                    }
                    for (TopLevelStreamElement elementJustSend : sendElements) {
                        connectionInternal.fireFirstLevelElementSendListeners(elementJustSend);
                    }
                }
                // written a certain amount.
                if (callbackBytesWritten > CALLBACK_MAX_BYTES_WRITEN) {
                    newInterestedOps |= SelectionKey.OP_WRITE;
                    callbackPreemtBecauseBytesWritten++;
                    break;
                }
            } else if (outgoingBuffer != null || pendingOutputFilterData) {
                pendingOutputFilterData = false;
                if (outgoingBuffer != null) {
                    totalBytesWrittenBeforeFilter += outgoingBuffer.remaining();
                    if (isLastPartOfElement) {
                        assert currentlyOutgonigTopLevelStreamElement != null;
                        currentlyOutgoingElements.add(currentlyOutgonigTopLevelStreamElement);
                    }
                }
                ByteBuffer outputFilterInputData = outgoingBuffer;
                // We can now null the outgoingBuffer since the filter step will take care of it from now on.
                outgoingBuffer = null;
                for (ListIterator<XmppInputOutputFilter> it = connectionInternal.getXmppInputOutputFilterBeginIterator(); it.hasNext(); ) {
                    XmppInputOutputFilter inputOutputFilter = it.next();
                    XmppInputOutputFilter.OutputResult outputResult;
                    try {
                        outputResult = inputOutputFilter.output(outputFilterInputData, isLastPartOfElement, destinationAddressChanged, moreDataAvailable);
                    } catch (IOException e) {
                        connectionInternal.notifyConnectionError(e);
                        break writeLoop;
                    }
                    newPendingOutputFilterData |= outputResult.pendingFilterData;
                    outputFilterInputData = outputResult.filteredOutputData;
                    if (outputFilterInputData != null) {
                        outputFilterInputData.flip();
                    }
                }
                // It is ok if outpuFilterInputData is 'null' here, this is expected behavior.
                if (outputFilterInputData != null && outputFilterInputData.hasRemaining()) {
                    filteredOutgoingBuffer = outputFilterInputData;
                } else {
                    filteredOutgoingBuffer = null;
                }
                // pending output data then we have a pending write request after read.
                if (filteredOutgoingBuffer == null && newPendingOutputFilterData) {
                    pendingWriteInterestAfterRead = true;
                }
                if (filteredOutgoingBuffer != null && isLastPartOfElement) {
                    bufferToElementMap.put(filteredOutgoingBuffer, new ArrayList<>(currentlyOutgoingElements));
                    currentlyOutgoingElements.clear();
                }
                // Reset that the destination address has changed.
                if (destinationAddressChanged) {
                    destinationAddressChanged = false;
                }
            } else if (outgoingCharSequenceIterator != null) {
                CharSequence nextCharSequence = outgoingCharSequenceIterator.next();
                outgoingBuffer = UTF8.encode(nextCharSequence);
                if (!outgoingCharSequenceIterator.hasNext()) {
                    outgoingCharSequenceIterator = null;
                    isLastPartOfElement = true;
                } else {
                    isLastPartOfElement = false;
                }
                final SmackDebugger debugger = connectionInternal.smackDebugger;
                if (debugger != null) {
                    if (outgoingStreamForDebugger == null) {
                        outgoingStreamForDebugger = new StringBuilder();
                    }
                    outgoingStreamForDebugger.append(nextCharSequence);
                    if (isLastPartOfElement) {
                        try {
                            outputDebugSplitter.append(outgoingStreamForDebugger);
                        } catch (IOException e) {
                            throw new AssertionError(e);
                        }
                        debugger.onOutgoingElementCompleted();
                        outgoingStreamForDebugger = null;
                    }
                }
            } else if (!connectionInternal.outgoingElementsQueue.isEmpty()) {
                currentlyOutgonigTopLevelStreamElement = connectionInternal.outgoingElementsQueue.poll();
                if (currentlyOutgonigTopLevelStreamElement instanceof Stanza) {
                    Stanza currentlyOutgoingStanza = (Stanza) currentlyOutgonigTopLevelStreamElement;
                    Jid currentDestinationAddress = currentlyOutgoingStanza.getTo();
                    destinationAddressChanged = !JidUtil.equals(lastDestinationAddress, currentDestinationAddress);
                    lastDestinationAddress = currentDestinationAddress;
                }
                CharSequence nextCharSequence = currentlyOutgonigTopLevelStreamElement.toXML(StreamOpen.CLIENT_NAMESPACE);
                if (nextCharSequence instanceof XmlStringBuilder) {
                    XmlStringBuilder xmlStringBuilder = (XmlStringBuilder) nextCharSequence;
                    XmlEnvironment outgoingStreamXmlEnvironment = connectionInternal.getOutgoingStreamXmlEnvironment();
                    outgoingCharSequenceIterator = xmlStringBuilder.toList(outgoingStreamXmlEnvironment).iterator();
                } else {
                    outgoingCharSequenceIterator = Collections.singletonList(nextCharSequence).iterator();
                }
                assert outgoingCharSequenceIterator != null;
            } else {
                // There is nothing more to write.
                break;
            }
        }
        pendingOutputFilterData = newPendingOutputFilterData;
        if (!pendingWriteInterestAfterRead && pendingOutputFilterData) {
            newInterestedOps |= SelectionKey.OP_WRITE;
        }
        readLoop: while (true) {
            // read a certain amount.
            if (callbackBytesRead > CALLBACK_MAX_BYTES_READ) {
                callbackPreemtBecauseBytesRead++;
                break;
            }
            int bytesRead;
            incomingBuffer.clear();
            try {
                bytesRead = selectedSocketChannel.read(incomingBuffer);
            } catch (IOException e) {
                handleReadWriteIoException(e);
                return;
            }
            if (bytesRead < 0) {
                LOGGER.finer("NIO read() returned " + bytesRead + " for " + this + ". This probably means that the TCP connection was terminated.");
                /*
                    IOException exception = new IOException("NIO read() returned " + bytesRead);
                    notifyConnectionError(exception);
                    */
                return;
            }
            if (!pendingInputFilterData) {
                if (bytesRead == 0) {
                    // Nothing more to read.
                    break;
                }
            } else {
                pendingInputFilterData = false;
            }
            if (pendingWriteInterestAfterRead) {
                // We have successfully read something and someone announced a write interest after a read. It is
                // now possible that a filter is now also able to write additional data (for example SSLEngine).
                pendingWriteInterestAfterRead = false;
                newInterestedOps |= SelectionKey.OP_WRITE;
            }
            callbackBytesRead += bytesRead;
            ByteBuffer filteredIncomingBuffer = incomingBuffer;
            for (ListIterator<XmppInputOutputFilter> it = connectionInternal.getXmppInputOutputFilterEndIterator(); it.hasPrevious(); ) {
                filteredIncomingBuffer.flip();
                ByteBuffer newFilteredIncomingBuffer;
                try {
                    newFilteredIncomingBuffer = it.previous().input(filteredIncomingBuffer);
                } catch (IOException e) {
                    connectionInternal.notifyConnectionError(e);
                    return;
                }
                if (newFilteredIncomingBuffer == null) {
                    break readLoop;
                }
                filteredIncomingBuffer = newFilteredIncomingBuffer;
            }
            final int bytesReadAfterFilter = filteredIncomingBuffer.flip().remaining();
            totalBytesReadAfterFilter += bytesReadAfterFilter;
            try {
                splitter.write(filteredIncomingBuffer);
            } catch (IOException e) {
                connectionInternal.notifyConnectionError(e);
                return;
            }
        }
    } finally {
        totalBytesWritten += callbackBytesWritten;
        totalBytesRead += callbackBytesRead;
        channelSelectedCallbackLock.unlock();
    }
    // Indicate that there is no reactor thread racing towards handling this selection key.
    final SelectionKeyAttachment selectionKeyAttachment = this.selectionKeyAttachment;
    if (selectionKeyAttachment != null) {
        selectionKeyAttachment.resetReactorThreadRacing();
    }
    // called resetReactorThreadRacing() a few lines above.
    if (!connectionInternal.outgoingElementsQueue.isEmpty()) {
        setWriteInterestAfterChannelSelectedCallback.incrementAndGet();
        newInterestedOps |= SelectionKey.OP_WRITE;
    }
    connectionInternal.setInterestOps(selectionKey, newInterestedOps);
}
Also used : ByteBuffer(java.nio.ByteBuffer) Buffer(java.nio.Buffer) SocketChannel(java.nio.channels.SocketChannel) XmppInputOutputFilter(org.jivesoftware.smack.XmppInputOutputFilter) SelectionKeyAttachment(org.jivesoftware.smack.SmackReactor.SelectionKeyAttachment) TopLevelStreamElement(org.jivesoftware.smack.packet.TopLevelStreamElement) XmlStringBuilder(org.jivesoftware.smack.util.XmlStringBuilder) DomainBareJid(org.jxmpp.jid.DomainBareJid) Jid(org.jxmpp.jid.Jid) Stanza(org.jivesoftware.smack.packet.Stanza) IOException(java.io.IOException) ListIterator(java.util.ListIterator) ByteBuffer(java.nio.ByteBuffer) Rfc6120TcpRemoteConnectionEndpoint(org.jivesoftware.smack.tcp.rce.Rfc6120TcpRemoteConnectionEndpoint) SmackDebugger(org.jivesoftware.smack.debugger.SmackDebugger) List(java.util.List) ArrayList(java.util.ArrayList) XmlStringBuilder(org.jivesoftware.smack.util.XmlStringBuilder) XmlEnvironment(org.jivesoftware.smack.packet.XmlEnvironment)

Aggregations

SelectionKeyAttachment (org.jivesoftware.smack.SmackReactor.SelectionKeyAttachment)2 IOException (java.io.IOException)1 Buffer (java.nio.Buffer)1 ByteBuffer (java.nio.ByteBuffer)1 SocketChannel (java.nio.channels.SocketChannel)1 ArrayList (java.util.ArrayList)1 List (java.util.List)1 ListIterator (java.util.ListIterator)1 XmppInputOutputFilter (org.jivesoftware.smack.XmppInputOutputFilter)1 SmackDebugger (org.jivesoftware.smack.debugger.SmackDebugger)1 Stanza (org.jivesoftware.smack.packet.Stanza)1 TopLevelStreamElement (org.jivesoftware.smack.packet.TopLevelStreamElement)1 XmlEnvironment (org.jivesoftware.smack.packet.XmlEnvironment)1 Rfc6120TcpRemoteConnectionEndpoint (org.jivesoftware.smack.tcp.rce.Rfc6120TcpRemoteConnectionEndpoint)1 XmlStringBuilder (org.jivesoftware.smack.util.XmlStringBuilder)1 DomainBareJid (org.jxmpp.jid.DomainBareJid)1 Jid (org.jxmpp.jid.Jid)1