On Thu, 17 Jul 2025 13:18:10 GMT, Jaikiran Pai <j...@openjdk.org> wrote:
>> Volkan Yazici has updated the pull request incrementally with one additional >> commit since the last revision: >> >> Improve docs on `IndexOutOfBoundsException` thrown >> >> Co-authored-by: Daniel Fuchs <67001856+df...@users.noreply.github.com> > > src/java.net.http/share/classes/java/net/http/HttpRequest.java line 746: > >> 744: */ >> 745: public static BodyPublisher ofFileChannel(FileChannel channel, >> long offset, long length) { >> 746: Objects.requireNonNull(channel, "channel"); > > The method's javadoc should have a `@throws NullPointerException if channel > is null`. Unless that's covered by a wider statement about null parameters > some place else? I couldn't find it in this class. Implemented in 8f66a32ec8b. > src/java.net.http/share/classes/java/net/http/HttpRequest.java line 747: > >> 745: public static BodyPublisher ofFileChannel(FileChannel channel, >> long offset, long length) { >> 746: Objects.requireNonNull(channel, "channel"); >> 747: return new RequestPublishers.FileChannelPublisher(channel, >> offset, length); > > Hello Volkan, I see that the constructor of `FileChannelPublisher` attempts > to determine the file size of the channel and if an `IOException` is thrown > then it gets propagated as `UncheckedIOException`. I think an alternate > approach and perhaps a better one would be to specify in this new method's > documentation that the `FileChannel`'s size will be first determined by this > method to verify that the given `offset` and the `length` are within bounds > of that size. That then allows us to add a throws clause to this method which > we merely propagate from the call of `FileChannel.size()`. Changed as suggested in 8f66a32ec8b. > src/java.net.http/share/classes/jdk/internal/net/http/RequestPublishers.java > line 433: > >> 431: this.channel = Objects.requireNonNull(channel, "channel"); >> 432: long fileSize = fileSize(channel); >> 433: Objects.checkFromIndexSize(offset, length, fileSize); > > My preference would be to move these file size determination and the > offset/length bounds check to the call site of this constructor. That way > it's much more visible at the place where this behaviour is specified (by the > public API). My motivation for keeping these checks at the `FileChannelPublisher::new` was 1. `FilePublisher` in the same file follows this convention too 2. Even though `jdk.internal.net.http` is not exported, the class is `public`. I wanted to ensure `FileChannelPublisher::new` doesn't accept invalid input @jaikiran, given these, 1. Do you still prefer me moving the checks to the `ofFileChannel`? 2. If yes, do you also want me to duplicate the checks in `FileChannelPublisher::new` too? > src/java.net.http/share/classes/jdk/internal/net/http/RequestPublishers.java > line 476: > >> 474: >> 475: @Override >> 476: public synchronized boolean hasNext() { > > In context of the synchronizations in this class, does a single instance of > `FileChannelIterator` gets accessed concurrently? I haven't fully grasped the > publisher/subscriber model through which this `FileChannelIterator` instance > would be handed out and how that then ends up as a `BodyPublisher` that's > accessed by the application. [Reactive Streams Specification for JVM](https://github.com/reactive-streams/reactive-streams-jvm) states the following: > #### Publisher > ... > `onSubscribe`, `onNext`, `onError` and `onComplete` signaled to a > `Subscriber` MUST be signaled _serially_ > ... > #### Subscriber > ... > A `Subscriber` MUST ensure that all calls on its `Subscription`'s request and > cancel methods are performed _serially_ Thus we indeed don't need synchronization in our publishers and subscribers. In fact, I explicitly investigated this concern for #23096, where we eventually dropped the synchronization for `LimitingSubscriber`, and this approach was blessed by @viktorklang-ora back then. The reason I opted for synchronization in `FileChannelIterator` was: 1. `StreamIterator` (located in the same class, and used for `ofFile()`) was synchronized too and I wanted to stick to the present convention 2. `PullPublisher` (which `StreamIterator`, `FileChannelIterator`, etc. instances are passed to _per subscription_) operates using a `SequentialScheduler`, and the _scheduler_ in the name made me more skeptical about concurrency implications But giving this a second thought, I agree that we should not implement redundant synchronization. I've removed synchronization for `FileChannelIterator` in b114fe9598e. > src/java.net.http/share/classes/jdk/internal/net/http/RequestPublishers.java > line 490: > >> 488: int readLength = channel.read(buffer, position); >> 489: // Short-circuit if `read()` has failed, e.g., due to >> file content being changed in the meantime >> 490: if (readLength < 0) { > > The `FileChannel.read(ByteBuffer, position)` API states: > >> If the given position is greater than or equal to the file's current size >> then no bytes are read. > > I think we might have to consider the situation where the underlying file's > size has reduced and this position is now past that size and the `read()` > call returning 0. Would that then end up in a situation where we have a > "never ending" BodyPublisher, which publishes nothing but hasn't terminated > either? The `FileChannel.read(ByteBuffer, position)` API further states the following: > [Method returns] The number of bytes read, possibly zero, or `-1` if the > given position is greater than or equal to the file's current size That is, the case you're describing is indicated by the `-1` return value, which we check in the next line. Plus, we already have a test for precisely the scenario that you're describing: `testFileModificationDuringPublisherRead`. > src/java.net.http/share/classes/jdk/internal/net/http/RequestPublishers.java > line 492: > >> 490: if (readLength < 0) { >> 491: // We *must* throw to signal that the request needs >> to be cancelled. >> 492: // Otherwise, the server will continue waiting data. > > The comment itself is a good idea but i think we should leave out the > reference to a "server" from this comment. Perhaps something like: "Throw to > signal that the request needs to be cancelled"? Changed as suggested in 183da9d0235. > src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java line > 372: > >> 370: >> 371: /** >> 372: * {@return a new {@link ByteBuffer} instance of configured >> capacity for the HTTP Client} > > I think instead of saying "configured capacity" we should say "instance of > {@link BUFSIZE} ...". This is an internal class so it should be OK to link to > this constant even if the constant itself isn't documented. Linking to > `BUFSIZE` makes it clear and easier to find what the actual capacity is. Changed as requested in eacd5da23bb. > src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java line > 384: > >> 382: * >> 383: * @param maxCapacity a buffer capacity, in bytes >> 384: * @throws IllegalArgumentException if {@code capacity < 0} > > Typo - should have been `if {@code maxCapacity < 0}` Fixed in da69840639e. ------------- PR Review Comment: https://git.openjdk.org/jdk/pull/26155#discussion_r2215596609 PR Review Comment: https://git.openjdk.org/jdk/pull/26155#discussion_r2215596735 PR Review Comment: https://git.openjdk.org/jdk/pull/26155#discussion_r2215596298 PR Review Comment: https://git.openjdk.org/jdk/pull/26155#discussion_r2215596074 PR Review Comment: https://git.openjdk.org/jdk/pull/26155#discussion_r2215595182 PR Review Comment: https://git.openjdk.org/jdk/pull/26155#discussion_r2215595719 PR Review Comment: https://git.openjdk.org/jdk/pull/26155#discussion_r2215594742 PR Review Comment: https://git.openjdk.org/jdk/pull/26155#discussion_r2215594608