mirror of https://github.com/apache/druid.git
Reference counting, better error handling for resources in groupBy v2. (#3268)
Refcounting prevents releasing the merge buffer, or closing the concurrent grouper, before the processing threads have all finished. The better error handling prevents an avalanche of per-runner exceptions when grouping resources are exhausted, by grouping those all up into a single merged exception.
This commit is contained in:
parent
188a4bc89a
commit
9b5523add3
|
@ -21,14 +21,13 @@ package io.druid.collections;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.base.Supplier;
|
import com.google.common.base.Supplier;
|
||||||
import com.metamx.common.ISE;
|
|
||||||
import com.metamx.common.logger.Logger;
|
import com.metamx.common.logger.Logger;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pool that pre-generates objects up to a limit, then permits possibly-blocking "take" operations.
|
* Pool that pre-generates objects up to a limit, then permits possibly-blocking "take" operations.
|
||||||
|
@ -60,60 +59,22 @@ public class BlockingPool<T>
|
||||||
*
|
*
|
||||||
* @throws InterruptedException if interrupted while waiting for a resource to become available
|
* @throws InterruptedException if interrupted while waiting for a resource to become available
|
||||||
*/
|
*/
|
||||||
public ResourceHolder<T> take(final long timeout) throws InterruptedException
|
public ReferenceCountingResourceHolder<T> take(final long timeout) throws InterruptedException
|
||||||
{
|
{
|
||||||
Preconditions.checkState(objects != null, "Pool was initialized with limit = 0, there are no objects to take.");
|
Preconditions.checkState(objects != null, "Pool was initialized with limit = 0, there are no objects to take.");
|
||||||
final T theObject = timeout >= 0 ? objects.poll(timeout, TimeUnit.MILLISECONDS) : objects.take();
|
final T theObject = timeout >= 0 ? objects.poll(timeout, TimeUnit.MILLISECONDS) : objects.take();
|
||||||
return theObject == null ? null : new ObjectResourceHolder(theObject);
|
return theObject == null ? null : new ReferenceCountingResourceHolder<>(
|
||||||
}
|
theObject,
|
||||||
|
new Closeable()
|
||||||
/**
|
{
|
||||||
* Similar to StupidPool.ObjectResourceHolder, except this one has no objectsCacheMaxCount, and it returns objects
|
@Override
|
||||||
* to the pool on finalize.
|
public void close() throws IOException
|
||||||
*/
|
{
|
||||||
private class ObjectResourceHolder implements ResourceHolder<T>
|
if (!objects.offer(theObject)) {
|
||||||
{
|
log.error("WTF?! Queue offer failed, uh oh...");
|
||||||
private AtomicBoolean closed = new AtomicBoolean(false);
|
}
|
||||||
private final T object;
|
}
|
||||||
|
|
||||||
public ObjectResourceHolder(final T object)
|
|
||||||
{
|
|
||||||
this.object = object;
|
|
||||||
}
|
|
||||||
|
|
||||||
// WARNING: it is entirely possible for a caller to hold onto the object and call "close", then still use that
|
|
||||||
// object even though it will be offered to someone else in BlockingPool.take
|
|
||||||
@Override
|
|
||||||
public T get()
|
|
||||||
{
|
|
||||||
if (closed.get()) {
|
|
||||||
throw new ISE("Already Closed!");
|
|
||||||
}
|
|
||||||
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close()
|
|
||||||
{
|
|
||||||
if (!closed.compareAndSet(false, true)) {
|
|
||||||
log.warn(new ISE("Already Closed!"), "Already closed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!objects.offer(object)) {
|
|
||||||
throw new ISE("WTF?! Queue offer failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void finalize() throws Throwable
|
|
||||||
{
|
|
||||||
if (closed.compareAndSet(false, true)) {
|
|
||||||
log.warn("Not closed! Object was[%s]. Returning to pool.", object);
|
|
||||||
if (!objects.offer(object)) {
|
|
||||||
log.error("WTF?! Queue offer failed during finalize, uh oh...");
|
|
||||||
}
|
}
|
||||||
}
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,138 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.collections;
|
||||||
|
|
||||||
|
import com.metamx.common.ISE;
|
||||||
|
import com.metamx.common.logger.Logger;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
public class ReferenceCountingResourceHolder<T> implements ResourceHolder<T>
|
||||||
|
{
|
||||||
|
private static final Logger log = new Logger(ReferenceCountingResourceHolder.class);
|
||||||
|
|
||||||
|
private final Object lock = new Object();
|
||||||
|
private final T object;
|
||||||
|
private final Closeable closer;
|
||||||
|
|
||||||
|
private int refcount = 1;
|
||||||
|
private boolean didClose = false;
|
||||||
|
|
||||||
|
public ReferenceCountingResourceHolder(final T object, final Closeable closer)
|
||||||
|
{
|
||||||
|
this.object = object;
|
||||||
|
this.closer = closer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T get()
|
||||||
|
{
|
||||||
|
synchronized (lock) {
|
||||||
|
if (refcount <= 0) {
|
||||||
|
throw new ISE("Already closed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Releaser increment()
|
||||||
|
{
|
||||||
|
synchronized (lock) {
|
||||||
|
if (refcount <= 0) {
|
||||||
|
throw new ISE("Already closed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
refcount++;
|
||||||
|
|
||||||
|
return new Releaser()
|
||||||
|
{
|
||||||
|
final AtomicBoolean didRelease = new AtomicBoolean();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
if (didRelease.compareAndSet(false, true)) {
|
||||||
|
decrement();
|
||||||
|
} else {
|
||||||
|
log.warn("WTF?! release called but we are already released!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void finalize() throws Throwable
|
||||||
|
{
|
||||||
|
if (didRelease.compareAndSet(false, true)) {
|
||||||
|
log.warn("Not released! Object was[%s], releasing on finalize of releaser.", object);
|
||||||
|
decrement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getReferenceCount()
|
||||||
|
{
|
||||||
|
synchronized (lock) {
|
||||||
|
return refcount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
synchronized (lock) {
|
||||||
|
if (!didClose) {
|
||||||
|
didClose = true;
|
||||||
|
decrement();
|
||||||
|
} else {
|
||||||
|
log.warn(new ISE("Already closed!"), "Already closed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void finalize() throws Throwable
|
||||||
|
{
|
||||||
|
synchronized (lock) {
|
||||||
|
if (!didClose) {
|
||||||
|
log.warn("Not closed! Object was[%s], closing on finalize of holder.", object);
|
||||||
|
didClose = true;
|
||||||
|
decrement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void decrement()
|
||||||
|
{
|
||||||
|
synchronized (lock) {
|
||||||
|
refcount--;
|
||||||
|
if (refcount <= 0) {
|
||||||
|
try {
|
||||||
|
closer.close();
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error(e, "WTF?! Close failed, uh oh...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.collections;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releaser is like Closeable, but doesn't throw IOExceptions.
|
||||||
|
*/
|
||||||
|
public interface Releaser extends Closeable
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
void close();
|
||||||
|
}
|
|
@ -20,24 +20,29 @@
|
||||||
package io.druid.query.groupby.epinephelinae;
|
package io.druid.query.groupby.epinephelinae;
|
||||||
|
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.base.Throwables;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
public class CloseableGrouperIterator<KeyType extends Comparable<KeyType>, T> implements Iterator<T>, Closeable
|
public class CloseableGrouperIterator<KeyType extends Comparable<KeyType>, T> implements Iterator<T>, Closeable
|
||||||
{
|
{
|
||||||
private final Grouper<KeyType> grouper;
|
private final Grouper<KeyType> grouper;
|
||||||
private final Function<Grouper.Entry<KeyType>, T> transformer;
|
private final Function<Grouper.Entry<KeyType>, T> transformer;
|
||||||
|
private final Closeable closer;
|
||||||
private final Iterator<Grouper.Entry<KeyType>> iterator;
|
private final Iterator<Grouper.Entry<KeyType>> iterator;
|
||||||
|
|
||||||
public CloseableGrouperIterator(
|
public CloseableGrouperIterator(
|
||||||
final Grouper<KeyType> grouper,
|
final Grouper<KeyType> grouper,
|
||||||
final boolean sorted,
|
final boolean sorted,
|
||||||
final Function<Grouper.Entry<KeyType>, T> transformer
|
final Function<Grouper.Entry<KeyType>, T> transformer,
|
||||||
|
final Closeable closer
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.grouper = grouper;
|
this.grouper = grouper;
|
||||||
this.transformer = transformer;
|
this.transformer = transformer;
|
||||||
|
this.closer = closer;
|
||||||
this.iterator = grouper.iterator(sorted);
|
this.iterator = grouper.iterator(sorted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,6 +67,13 @@ public class CloseableGrouperIterator<KeyType extends Comparable<KeyType>, T> im
|
||||||
@Override
|
@Override
|
||||||
public void close()
|
public void close()
|
||||||
{
|
{
|
||||||
grouper.close();
|
if (closer != null) {
|
||||||
|
try {
|
||||||
|
closer.close();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw Throwables.propagate(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,8 @@ import com.metamx.common.guava.ResourceClosingSequence;
|
||||||
import com.metamx.common.guava.Sequence;
|
import com.metamx.common.guava.Sequence;
|
||||||
import com.metamx.common.logger.Logger;
|
import com.metamx.common.logger.Logger;
|
||||||
import io.druid.collections.BlockingPool;
|
import io.druid.collections.BlockingPool;
|
||||||
import io.druid.collections.ResourceHolder;
|
import io.druid.collections.ReferenceCountingResourceHolder;
|
||||||
|
import io.druid.collections.Releaser;
|
||||||
import io.druid.data.input.MapBasedRow;
|
import io.druid.data.input.MapBasedRow;
|
||||||
import io.druid.data.input.Row;
|
import io.druid.data.input.Row;
|
||||||
import io.druid.query.AbstractPrioritizedCallable;
|
import io.druid.query.AbstractPrioritizedCallable;
|
||||||
|
@ -163,9 +164,10 @@ public class GroupByMergingQueryRunnerV2 implements QueryRunner
|
||||||
final Number timeout = query.getContextValue(QueryContextKeys.TIMEOUT, null);
|
final Number timeout = query.getContextValue(QueryContextKeys.TIMEOUT, null);
|
||||||
final long timeoutAt = timeout == null ? -1L : startTime + timeout.longValue();
|
final long timeoutAt = timeout == null ? -1L : startTime + timeout.longValue();
|
||||||
|
|
||||||
final ResourceHolder<ByteBuffer> mergeBufferHolder;
|
final ReferenceCountingResourceHolder<ByteBuffer> mergeBufferHolder;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// This will potentially block if there are no merge buffers left in the pool.
|
||||||
mergeBufferHolder = mergeBufferPool.take(timeout != null && timeout.longValue() > 0 ? timeout.longValue() : -1);
|
mergeBufferHolder = mergeBufferPool.take(timeout != null && timeout.longValue() > 0 ? timeout.longValue() : -1);
|
||||||
}
|
}
|
||||||
catch (InterruptedException e) {
|
catch (InterruptedException e) {
|
||||||
|
@ -204,6 +206,11 @@ public class GroupByMergingQueryRunnerV2 implements QueryRunner
|
||||||
final Row row
|
final Row row
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
if (theGrouper == null) {
|
||||||
|
// Pass-through null returns without doing more work.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
final long timestamp = row.getTimestampFromEpoch();
|
final long timestamp = row.getTimestampFromEpoch();
|
||||||
|
|
||||||
final String[] dimensions = new String[query.getDimensions().size()];
|
final String[] dimensions = new String[query.getDimensions().size()];
|
||||||
|
@ -215,7 +222,8 @@ public class GroupByMergingQueryRunnerV2 implements QueryRunner
|
||||||
columnSelectorFactory.setRow(row);
|
columnSelectorFactory.setRow(row);
|
||||||
final boolean didAggregate = theGrouper.aggregate(new GroupByMergingKey(timestamp, dimensions));
|
final boolean didAggregate = theGrouper.aggregate(new GroupByMergingKey(timestamp, dimensions));
|
||||||
if (!didAggregate) {
|
if (!didAggregate) {
|
||||||
throw new ISE("Grouping resources exhausted");
|
// null return means grouping resources were exhausted.
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
columnSelectorFactory.setRow(null);
|
columnSelectorFactory.setRow(null);
|
||||||
|
|
||||||
|
@ -225,53 +233,88 @@ public class GroupByMergingQueryRunnerV2 implements QueryRunner
|
||||||
|
|
||||||
final int priority = BaseQuery.getContextPriority(query, 0);
|
final int priority = BaseQuery.getContextPriority(query, 0);
|
||||||
|
|
||||||
ListenableFuture<List<Void>> futures = Futures.allAsList(
|
final ReferenceCountingResourceHolder<Grouper<GroupByMergingKey>> grouperHolder = new ReferenceCountingResourceHolder<>(
|
||||||
Lists.newArrayList(
|
grouper,
|
||||||
Iterables.transform(
|
new Closeable()
|
||||||
queryables,
|
{
|
||||||
new Function<QueryRunner<Row>, ListenableFuture<Void>>()
|
@Override
|
||||||
{
|
public void close() throws IOException
|
||||||
@Override
|
{
|
||||||
public ListenableFuture<Void> apply(final QueryRunner<Row> input)
|
grouper.close();
|
||||||
{
|
}
|
||||||
if (input == null) {
|
}
|
||||||
throw new ISE(
|
|
||||||
"Null queryRunner! Looks to be some segment unmapping action happening"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return exec.submit(
|
|
||||||
new AbstractPrioritizedCallable<Void>(priority)
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public Void call() throws Exception
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
input.run(queryForRunners, responseContext)
|
|
||||||
.accumulate(grouper, accumulator);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
catch (QueryInterruptedException e) {
|
|
||||||
throw Throwables.propagate(e);
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
log.error(e, "Exception with one of the sequences!");
|
|
||||||
throw Throwables.propagate(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
ListenableFuture<List<Boolean>> futures = Futures.allAsList(
|
||||||
|
Lists.newArrayList(
|
||||||
|
Iterables.transform(
|
||||||
|
queryables,
|
||||||
|
new Function<QueryRunner<Row>, ListenableFuture<Boolean>>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public ListenableFuture<Boolean> apply(final QueryRunner<Row> input)
|
||||||
|
{
|
||||||
|
if (input == null) {
|
||||||
|
throw new ISE(
|
||||||
|
"Null queryRunner! Looks to be some segment unmapping action happening"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Releaser bufferReleaser = mergeBufferHolder.increment();
|
||||||
|
try {
|
||||||
|
final Releaser grouperReleaser = grouperHolder.increment();
|
||||||
|
try {
|
||||||
|
return exec.submit(
|
||||||
|
new AbstractPrioritizedCallable<Boolean>(priority)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Boolean call() throws Exception
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
final Object retVal = input.run(queryForRunners, responseContext)
|
||||||
|
.accumulate(grouper, accumulator);
|
||||||
|
|
||||||
|
// Return true if OK, false if resources were exhausted.
|
||||||
|
return retVal == grouper;
|
||||||
|
}
|
||||||
|
catch (QueryInterruptedException e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error(e, "Exception with one of the sequences!");
|
||||||
|
throw Throwables.propagate(e);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
grouperReleaser.close();
|
||||||
|
bufferReleaser.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
// Exception caught while submitting the task; release resources.
|
||||||
|
grouperReleaser.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
// Exception caught while submitting the task; release resources.
|
||||||
|
bufferReleaser.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
waitForFutureCompletion(query, futures, timeoutAt - processingStartTime);
|
waitForFutureCompletion(query, futures, timeoutAt - processingStartTime);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
grouper.close();
|
// Exception caught while creating or waiting for futures; release resources.
|
||||||
|
grouperHolder.close();
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,6 +346,14 @@ public class GroupByMergingQueryRunnerV2 implements QueryRunner
|
||||||
theMap
|
theMap
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
new Closeable()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException
|
||||||
|
{
|
||||||
|
grouperHolder.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -335,18 +386,26 @@ public class GroupByMergingQueryRunnerV2 implements QueryRunner
|
||||||
|
|
||||||
private void waitForFutureCompletion(
|
private void waitForFutureCompletion(
|
||||||
GroupByQuery query,
|
GroupByQuery query,
|
||||||
ListenableFuture<?> future,
|
ListenableFuture<List<Boolean>> future,
|
||||||
long timeout
|
long timeout
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
final List<Boolean> results;
|
||||||
if (queryWatcher != null) {
|
if (queryWatcher != null) {
|
||||||
queryWatcher.registerQuery(query, future);
|
queryWatcher.registerQuery(query, future);
|
||||||
}
|
}
|
||||||
if (timeout <= 0) {
|
if (timeout <= 0) {
|
||||||
future.get();
|
results = future.get();
|
||||||
} else {
|
} else {
|
||||||
future.get(timeout, TimeUnit.MILLISECONDS);
|
results = future.get(timeout, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Boolean result : results) {
|
||||||
|
if (!result) {
|
||||||
|
future.cancel(true);
|
||||||
|
throw new ISE("Grouping resources exhausted");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (InterruptedException e) {
|
catch (InterruptedException e) {
|
||||||
|
|
|
@ -314,6 +314,14 @@ outer:
|
||||||
|
|
||||||
return new MapBasedRow(timestamp, theMap);
|
return new MapBasedRow(timestamp, theMap);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
new Closeable()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException
|
||||||
|
{
|
||||||
|
grouper.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -59,10 +59,19 @@ public class LimitedTemporaryStorage implements Closeable
|
||||||
this.maxBytesUsed = maxBytesUsed;
|
this.maxBytesUsed = maxBytesUsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new temporary file. All methods of the returned output stream may throw
|
||||||
|
* {@link TemporaryStorageFullException} if the temporary storage area fills up.
|
||||||
|
*
|
||||||
|
* @return output stream to the file
|
||||||
|
*
|
||||||
|
* @throws TemporaryStorageFullException if the temporary storage area is full
|
||||||
|
* @throws IOException if something goes wrong while creating the file
|
||||||
|
*/
|
||||||
public LimitedOutputStream createFile() throws IOException
|
public LimitedOutputStream createFile() throws IOException
|
||||||
{
|
{
|
||||||
if (bytesUsed.get() >= maxBytesUsed) {
|
if (bytesUsed.get() >= maxBytesUsed) {
|
||||||
throwFullError();
|
throw new TemporaryStorageFullException(maxBytesUsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (files) {
|
synchronized (files) {
|
||||||
|
@ -158,14 +167,9 @@ public class LimitedTemporaryStorage implements Closeable
|
||||||
private void grab(int n) throws IOException
|
private void grab(int n) throws IOException
|
||||||
{
|
{
|
||||||
if (bytesUsed.addAndGet(n) > maxBytesUsed) {
|
if (bytesUsed.addAndGet(n) > maxBytesUsed) {
|
||||||
throwFullError();
|
throw new TemporaryStorageFullException(maxBytesUsed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void throwFullError() throws IOException
|
|
||||||
{
|
|
||||||
throw new IOException(String.format("Cannot write to disk, hit limit of %,d bytes.", maxBytesUsed));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,7 +93,15 @@ public class SpillingGrouper<KeyType extends Comparable<KeyType>> implements Gro
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
// Warning: this can potentially block up a processing thread for a while.
|
// Warning: this can potentially block up a processing thread for a while.
|
||||||
spill();
|
try {
|
||||||
|
spill();
|
||||||
|
}
|
||||||
|
catch (TemporaryStorageFullException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw Throwables.propagate(e);
|
||||||
|
}
|
||||||
return grouper.aggregate(key, keyHash);
|
return grouper.aggregate(key, keyHash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,14 +162,16 @@ public class SpillingGrouper<KeyType extends Comparable<KeyType>> implements Gro
|
||||||
return Groupers.mergeIterators(iterators, sorted);
|
return Groupers.mergeIterators(iterators, sorted);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void spill()
|
private void spill() throws IOException
|
||||||
{
|
{
|
||||||
|
final File outFile;
|
||||||
|
|
||||||
try (
|
try (
|
||||||
final LimitedTemporaryStorage.LimitedOutputStream out = temporaryStorage.createFile();
|
final LimitedTemporaryStorage.LimitedOutputStream out = temporaryStorage.createFile();
|
||||||
final LZ4BlockOutputStream compressedOut = new LZ4BlockOutputStream(out);
|
final LZ4BlockOutputStream compressedOut = new LZ4BlockOutputStream(out);
|
||||||
final JsonGenerator jsonGenerator = spillMapper.getFactory().createGenerator(compressedOut)
|
final JsonGenerator jsonGenerator = spillMapper.getFactory().createGenerator(compressedOut)
|
||||||
) {
|
) {
|
||||||
files.add(out.getFile());
|
outFile = out.getFile();
|
||||||
final Iterator<Entry<KeyType>> it = grouper.iterator(true);
|
final Iterator<Entry<KeyType>> it = grouper.iterator(true);
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
if (Thread.interrupted()) {
|
if (Thread.interrupted()) {
|
||||||
|
@ -171,10 +181,8 @@ public class SpillingGrouper<KeyType extends Comparable<KeyType>> implements Gro
|
||||||
jsonGenerator.writeObject(it.next());
|
jsonGenerator.writeObject(it.next());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
|
||||||
throw Throwables.propagate(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
files.add(outFile);
|
||||||
grouper.reset();
|
grouper.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.query.groupby.epinephelinae;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class TemporaryStorageFullException extends IOException
|
||||||
|
{
|
||||||
|
public TemporaryStorageFullException(final long maxBytesUsed)
|
||||||
|
{
|
||||||
|
super(String.format("Cannot write to disk, hit limit of %,d bytes.", maxBytesUsed));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue