-
Notifications
You must be signed in to change notification settings - Fork 546
[SYSTEMDS-3946] Merge PR Enable sending of large (>2GiB) FederatedRequests #2591
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
+1,264
−16
Closed
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
5e1d0a2
[SYSTEMDS-3946] Enable sending of large (>2GiB) FederatedRequests and…
Biranavan-Parameswaran 0a01a8b
[SYSTEMDS-3946] Streaming codec routing, chunk tuning, tests, review …
Biranavan-Parameswaran 573fd85
chore: trigger PR update
Biranavan-Parameswaran 65e0840
[SYSTEMDS-3946] Fix Eclipse formatting on PR-edited lines
Biranavan-Parameswaran 72135fb
[SYSTEMDS-3946] Test chunk codec error propagation and frame release
Biranavan-Parameswaran e1e3ca4
[SYSTEMDS-3946] Rename chunk decoder locals per review
Biranavan-Parameswaran ea99511
[SYSTEMDS-3946] Javadoc for the streaming chunk codec
Biranavan-Parameswaran bb8136b
[SYSTEMDS-3946] Test unknown chunk frame type
Biranavan-Parameswaran 3cc4f03
refactor(main/runtime/controlprogram/federated/FederatedChunkProtocol…
ywcb00 d9a9e1c
refactor(test/functions/federated/io/FederatedMaxPayloadTest.java): m…
ywcb00 08c05f6
fix(main/runtime/controlprogram/federated/FederatedData.java): add th…
ywcb00 a7b3da4
fix(main/runtime/controlprogram/federated/FederatedResponse.java): mo…
ywcb00 6ccf70c
chore(**): remove unused imports
ywcb00 c17574a
fix(main/runtime/controlprogram/federated/FederatedChunkDecoder.java)…
Biranavan-Parameswaran File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
241 changes: 241 additions & 0 deletions
241
src/main/java/org/apache/sysds/runtime/controlprogram/federated/FederatedChunkDecoder.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,241 @@ | ||
| /* | ||
| * 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.sysds.runtime.controlprogram.federated; | ||
|
|
||
| import java.io.IOException; | ||
| import java.io.InputStream; | ||
| import java.io.ObjectInputStream; | ||
| import java.io.ObjectStreamClass; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.util.List; | ||
| import java.util.concurrent.BlockingQueue; | ||
| import java.util.concurrent.LinkedBlockingQueue; | ||
|
|
||
| import org.apache.sysds.runtime.util.CommonThreadPool; | ||
|
|
||
| import io.netty.buffer.ByteBuf; | ||
| import io.netty.channel.ChannelHandlerContext; | ||
| import io.netty.handler.codec.MessageToMessageDecoder; | ||
|
|
||
| public class FederatedChunkDecoder extends MessageToMessageDecoder<ByteBuf> { | ||
| private static final Object END_OF_STREAM = new Object(); | ||
| // stop reading at QUEUE_DEPTH, resume at half: gap avoids autoRead thrash | ||
| private static final int LOW_WATERMARK = FederatedChunkProtocol.QUEUE_DEPTH / 2; | ||
|
|
||
| private final BlockingQueue<Object> _payloads = new LinkedBlockingQueue<>(); | ||
| private boolean _started; | ||
| private volatile boolean _throttled; | ||
|
|
||
| @Override | ||
| protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) { | ||
| startReader(ctx); | ||
| byte type = buf.readByte(); | ||
| int len = buf.readInt(); | ||
| switch(type) { | ||
| case FederatedChunkProtocol.TYPE_DATA: | ||
| _payloads.add(readBytes(buf, len)); | ||
| break; | ||
| case FederatedChunkProtocol.TYPE_END: | ||
| _payloads.add(END_OF_STREAM); | ||
| break; | ||
| case FederatedChunkProtocol.TYPE_ERROR: | ||
| _payloads.add(new IOException(buf.toString(buf.readerIndex(), len, StandardCharsets.UTF_8))); | ||
| break; | ||
| default: | ||
| _payloads.add(new IOException("Unknown federated chunk frame type: " + type)); | ||
| break; | ||
| } | ||
| if(_payloads.size() >= FederatedChunkProtocol.QUEUE_DEPTH) { | ||
| _throttled = true; | ||
| ctx.channel().config().setAutoRead(false); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void channelInactive(ChannelHandlerContext ctx) throws Exception { | ||
| _payloads.add(new IOException("Channel closed before the federated chunk stream ended.")); | ||
| super.channelInactive(ctx); | ||
| } | ||
|
|
||
| /** | ||
| * Start the deserializer on a pool thread, at most once per channel. | ||
| * | ||
| * @param ctx handler context | ||
| */ | ||
| private void startReader(ChannelHandlerContext ctx) { | ||
| if(_started) | ||
| return; | ||
| _started = true; | ||
| CommonThreadPool.getDynamicPool().execute(() -> runDeserializer(ctx)); | ||
| } | ||
|
|
||
| /** | ||
| * Read one object from the queued payloads and fire it up the pipeline. A failure is fired as an exception on the | ||
| * event loop instead. | ||
| * | ||
| * @param ctx handler context | ||
| */ | ||
| private void runDeserializer(ChannelHandlerContext ctx) { | ||
| try(PayloadInputStream in = new PayloadInputStream(this, ctx); | ||
| ObjectInputStream ois = getObjectInputStream(in)) { | ||
| Object msg = ois.readObject(); | ||
|
ywcb00 marked this conversation as resolved.
|
||
| in.skipToEndOfStream(); | ||
| ctx.channel().eventLoop().execute(() -> ctx.fireChannelRead(msg)); | ||
| } | ||
| catch(Throwable t) { | ||
| ctx.channel().eventLoop().execute(() -> ctx.fireExceptionCaught(t)); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Take the next queued payload, blocking until one arrives. | ||
| * | ||
| * @return payload bytes, the end of stream marker or a queued failure | ||
| * @throws InterruptedException on interrupt while the queue is empty | ||
| */ | ||
| private Object nextPayload() throws InterruptedException { | ||
| return _payloads.take(); | ||
| } | ||
|
|
||
| /** | ||
| * Re-enable channel reads once the payload queue has drained to the low watermark. | ||
| * | ||
| * @param ctx handler context | ||
| */ | ||
| private void resumeReadingIfDrained(ChannelHandlerContext ctx) { | ||
| if(_throttled && _payloads.size() <= LOW_WATERMARK) { | ||
| _throttled = false; | ||
| ctx.channel().eventLoop().execute(() -> ctx.channel().config().setAutoRead(true)); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Create an object input stream using the system class loader. | ||
| * | ||
| * @param in stream of payload bytes | ||
| * @return object input stream | ||
| * @throws IOException on stream header failure | ||
| */ | ||
| private static ObjectInputStream getObjectInputStream(InputStream in) throws IOException { | ||
| return new ObjectInputStream(in) { | ||
| @Override | ||
| protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { | ||
| try { | ||
| return Class.forName(desc.getName(), false, ClassLoader.getSystemClassLoader()); | ||
| } | ||
| catch(ClassNotFoundException e) { | ||
| return super.resolveClass(desc); | ||
| } | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| private static byte[] readBytes(ByteBuf buf, int len) { | ||
| byte[] bytes = new byte[len]; | ||
| buf.readBytes(bytes); | ||
| return bytes; | ||
| } | ||
|
|
||
| private static final class PayloadInputStream extends InputStream { | ||
| private static final byte[] EMPTY = new byte[0]; | ||
|
|
||
| private final FederatedChunkDecoder _decoder; | ||
| private final ChannelHandlerContext _ctx; | ||
| private byte[] _current = EMPTY; | ||
| private int _pos; | ||
| private boolean _eof; | ||
|
|
||
| PayloadInputStream(FederatedChunkDecoder decoder, ChannelHandlerContext ctx) { | ||
| _decoder = decoder; | ||
| _ctx = ctx; | ||
| } | ||
|
|
||
| @Override | ||
| public int read() throws IOException { | ||
| if(!ensureCurrent()) | ||
| return -1; | ||
| return _current[_pos++] & 0xff; | ||
| } | ||
|
|
||
| @Override | ||
| public int read(byte[] b, int off, int len) throws IOException { | ||
| if(!ensureCurrent()) | ||
| return -1; | ||
| int n = Math.min(len, _current.length - _pos); | ||
| System.arraycopy(_current, _pos, b, off, n); | ||
| _pos += n; | ||
| return n; | ||
| } | ||
|
|
||
| /** | ||
| * Take the next payload and resume reading if the queue has drained. | ||
| * | ||
| * @return payload bytes, the end of stream marker or a queued failure | ||
| * @throws IOException on interrupt | ||
| */ | ||
| private Object take() throws IOException { | ||
| try { | ||
| Object next = _decoder.nextPayload(); | ||
| _decoder.resumeReadingIfDrained(_ctx); | ||
| return next; | ||
| } | ||
| catch(InterruptedException e) { | ||
| Thread.currentThread().interrupt(); | ||
| throw new IOException(e); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Advance to the next payload when the current one is exhausted. A queued failure is rethrown as an | ||
| * IOException. | ||
| * | ||
| * @return true if bytes are available, false at end of stream | ||
| * @throws IOException on a queued failure or on interrupt | ||
| */ | ||
| private boolean ensureCurrent() throws IOException { | ||
| while(_pos == _current.length) { | ||
| if(_eof) | ||
| return false; | ||
| Object next = take(); | ||
| if(next == END_OF_STREAM) { | ||
| _eof = true; | ||
| return false; | ||
| } | ||
| if(next instanceof Throwable) | ||
| throw new IOException((Throwable) next); | ||
| _current = (byte[]) next; | ||
| _pos = 0; | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Consume the remaining frames up to and including the end of stream marker. | ||
| * | ||
| * @throws IOException on a queued failure or on interrupt | ||
| */ | ||
| void skipToEndOfStream() throws IOException { | ||
| while(!_eof) { | ||
| _pos = _current.length; | ||
| ensureCurrent(); | ||
| } | ||
| } | ||
|
ywcb00 marked this conversation as resolved.
|
||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.