-
Notifications
You must be signed in to change notification settings - Fork 2
Change getStreams to use multiple clients #19
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
base: flight-jdbc-driver
Are you sure you want to change the base?
Changes from all commits
d30973d
4d5a63e
21de4b3
108ddbb
e53e360
45100e4
14cfe9e
68d4d08
c70f07b
efa671f
711cd89
5e49a23
ac100df
25bcd99
2cdd2b3
39631a9
d750b25
e0a2bff
a8d1d53
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,14 +20,17 @@ | |
| import java.io.IOException; | ||
| import java.security.GeneralSecurityException; | ||
| import java.sql.SQLException; | ||
| import java.util.ArrayList; | ||
| import java.util.Arrays; | ||
| import java.util.Collection; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.NoSuchElementException; | ||
| import java.util.Set; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| import org.apache.arrow.driver.jdbc.client.utils.ClientAuthenticationUtils; | ||
| import org.apache.arrow.driver.jdbc.client.utils.KeyedFlightSqlClientObjectPool; | ||
| import org.apache.arrow.driver.jdbc.client.utils.KeyedFlightSqlClientObjectPoolFactory; | ||
| import org.apache.arrow.flight.CallOption; | ||
| import org.apache.arrow.flight.FlightClient; | ||
| import org.apache.arrow.flight.FlightClientMiddleware; | ||
|
|
@@ -49,31 +52,39 @@ | |
| import org.apache.arrow.util.Preconditions; | ||
| import org.apache.arrow.vector.types.pojo.Schema; | ||
| import org.apache.calcite.avatica.Meta.StatementType; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| /** | ||
| * A {@link FlightSqlClient} handler. | ||
| */ | ||
| public final class ArrowFlightSqlClientHandler implements AutoCloseable { | ||
|
|
||
| private static final Logger logger = LoggerFactory.getLogger(ArrowFlightSqlClientHandler.class); | ||
| private final FlightSqlClient sqlClient; | ||
| private final Set<CallOption> options = new HashSet<>(); | ||
| private final KeyedFlightSqlClientObjectPool flightSqlClientPool; | ||
|
|
||
| ArrowFlightSqlClientHandler(final FlightSqlClient sqlClient, | ||
| final Collection<CallOption> options) { | ||
| BufferAllocator allocator, final Collection<CallOption> options) { | ||
| this.options.addAll(options); | ||
| this.flightSqlClientPool = new KeyedFlightSqlClientObjectPool( | ||
| new KeyedFlightSqlClientObjectPoolFactory(allocator)); | ||
| this.sqlClient = Preconditions.checkNotNull(sqlClient); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new {@link ArrowFlightSqlClientHandler} from the provided {@code client} and {@code options}. | ||
| * | ||
| * @param client the {@link FlightClient} to manage under a {@link FlightSqlClient} wrapper. | ||
| * @param allocator the {@link BufferAllocator} used to create the client. | ||
| * @param options the {@link CallOption}s to persist in between subsequent client calls. | ||
| * @return a new {@link ArrowFlightSqlClientHandler}. | ||
| */ | ||
| public static ArrowFlightSqlClientHandler createNewHandler(final FlightClient client, | ||
| final BufferAllocator allocator, | ||
| final Collection<CallOption> options) { | ||
| return new ArrowFlightSqlClientHandler(new FlightSqlClient(client), options); | ||
| return new ArrowFlightSqlClientHandler(new FlightSqlClient(client), allocator, options); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -92,11 +103,36 @@ private CallOption[] getOptions() { | |
| * @param flightInfo The {@link FlightInfo} instance from which to fetch results. | ||
| * @return a {@code FlightStream} of results. | ||
| */ | ||
| public List<FlightStream> getStreams(final FlightInfo flightInfo) { | ||
| return flightInfo.getEndpoints().stream() | ||
| .map(FlightEndpoint::getTicket) | ||
| .map(ticket -> sqlClient.getStream(ticket, getOptions())) | ||
| .collect(Collectors.toList()); | ||
| public List<FlightStream> getStreams(final FlightInfo flightInfo) throws SQLException { | ||
| final List<FlightStream> allStreams = new ArrayList<>(); | ||
|
|
||
| for (final FlightEndpoint endpoint : flightInfo.getEndpoints()) { | ||
| List<Location> locations = endpoint.getLocations(); | ||
| if (locations.isEmpty()) { | ||
| allStreams.add(sqlClient.getStream(endpoint.getTicket(), getOptions())); | ||
| continue; | ||
| } | ||
| final Location location = locations.get(0); // purposefully discard other locations | ||
|
|
||
| logger.info(String.format("Getting a client for location %s.", location)); | ||
| FlightSqlClient flightSqlClient; | ||
| try { | ||
| flightSqlClient = flightSqlClientPool.borrowObject(location); | ||
| } catch (final NoSuchElementException e) { | ||
| try { | ||
| flightSqlClientPool.addObject(location); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would think this happens automatically rather than needing try/catch. |
||
| flightSqlClient = flightSqlClientPool.borrowObject(location); | ||
| } catch (final Exception addObjectEx) { | ||
| throw new SQLException("Failed to create and get a new FlightSqlClient in the ObjectPool.", addObjectEx); | ||
| } | ||
| } catch (final Exception borrowObjectEx) { | ||
| throw new SQLException("Failed to borrow a FlightSqlClient from the ObjectPool", borrowObjectEx); | ||
| } | ||
|
|
||
| allStreams.add(flightSqlClient.getStream(endpoint.getTicket(), getOptions())); | ||
| flightSqlClientPool.returnObject(location, flightSqlClient); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We cannot return the client to the pool until we are done getting the stream. Otherwise it allows another query being executed to re-use the client while it's still being used.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we can write an auto-closeable wrapper on top of FlightStream that returns to the pool on closure. |
||
| } | ||
| return allStreams; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -114,6 +150,7 @@ public FlightInfo getInfo(final String query) { | |
| public void close() throws SQLException { | ||
| try { | ||
| AutoCloseables.close(sqlClient); | ||
| flightSqlClientPool.close(); | ||
| } catch (final Exception e) { | ||
| throw new SQLException("Failed to clean up client resources.", e); | ||
| } | ||
|
|
@@ -529,7 +566,7 @@ public ArrowFlightSqlClientHandler build() throws SQLException { | |
| ClientAuthenticationUtils.getAuthenticate( | ||
| client, new CredentialCallOption(new BearerCredentialWriter(token)))); | ||
| } | ||
| return ArrowFlightSqlClientHandler.createNewHandler(client, options); | ||
| return ArrowFlightSqlClientHandler.createNewHandler(client, allocator, options); | ||
|
|
||
| } catch (final IllegalArgumentException | GeneralSecurityException | IOException | FlightRuntimeException e) { | ||
| final SQLException originalException = new SQLException(e); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| /* | ||
| * 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.arrow.driver.jdbc.client.utils; | ||
|
|
||
| import org.apache.arrow.flight.Location; | ||
| import org.apache.arrow.flight.sql.FlightSqlClient; | ||
| import org.apache.commons.pool2.KeyedPooledObjectFactory; | ||
| import org.apache.commons.pool2.impl.GenericKeyedObjectPool; | ||
|
|
||
| /** | ||
| * Draft. | ||
| */ | ||
| public class KeyedFlightSqlClientObjectPool extends GenericKeyedObjectPool<Location, FlightSqlClient> { | ||
|
|
||
| public KeyedFlightSqlClientObjectPool(KeyedPooledObjectFactory<Location, FlightSqlClient> factory) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The casting of the factory to call a close method on it is weird.
|
||
| super(factory); | ||
| } | ||
|
|
||
| @Override | ||
| public synchronized void close() { | ||
| ((KeyedFlightSqlClientObjectPoolFactory) getFactory()).closeAllocator(); | ||
| super.close(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you should actually call super.close() first to clean up the clients which are depending on the allocator, then clean up the allocator. (Normally the right thing to do is do child class clean-up, then base class clean-up but I think the logic needs to be different in this scenario). |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| /* | ||
| * 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.arrow.driver.jdbc.client.utils; | ||
|
|
||
| import java.util.concurrent.atomic.AtomicInteger; | ||
|
|
||
| import org.apache.arrow.flight.FlightClient; | ||
| import org.apache.arrow.flight.Location; | ||
| import org.apache.arrow.flight.sql.FlightSqlClient; | ||
| import org.apache.arrow.memory.BufferAllocator; | ||
| import org.apache.commons.pool2.BaseKeyedPooledObjectFactory; | ||
| import org.apache.commons.pool2.PooledObject; | ||
| import org.apache.commons.pool2.impl.DefaultPooledObject; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| /** | ||
| * Draft. | ||
| */ | ||
| public class KeyedFlightSqlClientObjectPoolFactory extends BaseKeyedPooledObjectFactory<Location, FlightSqlClient> { | ||
| private static final Logger logger = LoggerFactory.getLogger(KeyedFlightSqlClientObjectPoolFactory.class); | ||
| private final BufferAllocator parentAllocator; | ||
| private final AtomicInteger clientCounter = new AtomicInteger(); | ||
|
|
||
| /** | ||
| * Draft. | ||
| * @param parentAllocator allocator. | ||
| */ | ||
| public KeyedFlightSqlClientObjectPoolFactory(final BufferAllocator parentAllocator) { | ||
| super(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: let's avoid default super calls. |
||
| this.parentAllocator = parentAllocator | ||
| .newChildAllocator("KeyedFlightSqlClientObjectPoolFactory", 0, parentAllocator.getLimit()); | ||
| } | ||
|
|
||
| @Override | ||
| public FlightSqlClient create(Location key) throws Exception { | ||
| logger.info("Trying to create a new FlightSqlClient."); | ||
| return new FlightSqlClient( | ||
| FlightClient.builder( | ||
| parentAllocator.newChildAllocator( | ||
| "flight-sql-client-pool_id-" + clientCounter.getAndIncrement(), | ||
| 0, | ||
| parentAllocator.getLimit()), | ||
| key).build()); | ||
| } | ||
|
|
||
| public void closeAllocator() { | ||
| parentAllocator.getChildAllocators().forEach(BufferAllocator::close); | ||
| parentAllocator.close(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure about closing the parent allocator here... I don't see a call to close() being removed which means prior to this patch we were either leaking an allocator or we are double-closing this. |
||
| } | ||
|
|
||
| @Override | ||
| public PooledObject<FlightSqlClient> wrap(FlightSqlClient value) { | ||
| logger.info("Wrapping an existing FlightSqlClient."); | ||
| return new DefaultPooledObject<>(value); | ||
| } | ||
|
|
||
| @Override | ||
| public void destroyObject(Location key, PooledObject<FlightSqlClient> p) throws Exception { | ||
| logger.info("Closing a client."); | ||
| p.getObject().close(); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't use String.format() with loggers. This executes the work to generate a string, even if logging is off.
Use the logger directly. Note that placeholders are curly braces in SLF4J eg:
logger.info("Getting client for location {}.", location);
please fix this throughout the PR.