Skip to content

Commit 48f2fb1

Browse files
shangm2NikhilCollooru
authored andcommitted
Use a safe eventLoop wrapper to avoid thread shutdown and fail the task correctly
1 parent 50df5e4 commit 48f2fb1

File tree

3 files changed

+161
-19
lines changed

3 files changed

+161
-19
lines changed

presto-main/pom.xml

+5
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,11 @@
509509
<artifactId>netty-transport</artifactId>
510510
</dependency>
511511

512+
<dependency>
513+
<groupId>io.netty</groupId>
514+
<artifactId>netty-common</artifactId>
515+
</dependency>
516+
512517
<dependency>
513518
<groupId>com.squareup.okhttp3</groupId>
514519
<artifactId>mockwebserver</artifactId>

presto-main/src/main/java/com/facebook/presto/server/remotetask/HttpRemoteTask.java

+109-16
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@
7676
import com.sun.management.ThreadMXBean;
7777
import io.airlift.units.DataSize;
7878
import io.airlift.units.Duration;
79-
import io.netty.channel.EventLoop;
8079
import it.unimi.dsi.fastutil.longs.LongArrayList;
8180
import org.joda.time.DateTime;
8281

@@ -143,7 +142,7 @@
143142
* This class now uses an event loop concurrency model to eliminate the need for explicit synchronization:
144143
* <ul>
145144
* <li>All mutable state access and modifications are performed on a single dedicated event loop thread</li>
146-
* <li>External threads submit operations to the event loop using {@code taskEventLoop.execute()}</li>
145+
* <li>External threads submit operations to the event loop using {@code safeExecuteOnEventLoop()}</li>
147146
* <li>The event loop serializes all operations, eliminating race conditions without using locks</li>
148147
* </ul>
149148
* <p>
@@ -230,9 +229,9 @@ public final class HttpRemoteTask
230229
private final DecayCounter taskUpdateRequestSize;
231230
private final SchedulerStatsTracker schedulerStatsTracker;
232231

233-
private final EventLoop taskEventLoop;
232+
private final HttpRemoteTaskFactory.SafeEventLoop taskEventLoop;
234233

235-
public HttpRemoteTask(
234+
public static HttpRemoteTask createHttpRemoteTask(
236235
Session session,
237236
TaskId taskId,
238237
String nodeId,
@@ -267,7 +266,82 @@ public HttpRemoteTask(
267266
HandleResolver handleResolver,
268267
ConnectorTypeSerdeManager connectorTypeSerdeManager,
269268
SchedulerStatsTracker schedulerStatsTracker,
270-
EventLoop taskEventLoop)
269+
HttpRemoteTaskFactory.SafeEventLoop taskEventLoop)
270+
{
271+
HttpRemoteTask task = new HttpRemoteTask(session,
272+
taskId,
273+
nodeId,
274+
location,
275+
remoteLocation,
276+
planFragment,
277+
initialSplits,
278+
outputBuffers,
279+
httpClient,
280+
maxErrorDuration,
281+
taskStatusRefreshMaxWait,
282+
taskInfoRefreshMaxWait,
283+
taskInfoUpdateInterval,
284+
summarizeTaskInfo,
285+
taskStatusCodec,
286+
taskInfoCodec,
287+
taskInfoJsonCodec,
288+
taskUpdateRequestCodec,
289+
planFragmentCodec,
290+
metadataUpdatesCodec,
291+
nodeStatsTracker,
292+
stats,
293+
binaryTransportEnabled,
294+
thriftTransportEnabled,
295+
taskInfoThriftTransportEnabled,
296+
thriftProtocol,
297+
tableWriteInfo,
298+
maxTaskUpdateSizeInBytes,
299+
metadataManager,
300+
queryManager,
301+
taskUpdateRequestSize,
302+
handleResolver,
303+
connectorTypeSerdeManager,
304+
schedulerStatsTracker,
305+
taskEventLoop);
306+
task.initialize();
307+
return task;
308+
}
309+
310+
private HttpRemoteTask(Session session,
311+
TaskId taskId,
312+
String nodeId,
313+
URI location,
314+
URI remoteLocation,
315+
PlanFragment planFragment,
316+
Multimap<PlanNodeId, Split> initialSplits,
317+
OutputBuffers outputBuffers,
318+
HttpClient httpClient,
319+
Duration maxErrorDuration,
320+
Duration taskStatusRefreshMaxWait,
321+
Duration taskInfoRefreshMaxWait,
322+
Duration taskInfoUpdateInterval,
323+
boolean summarizeTaskInfo,
324+
Codec<TaskStatus> taskStatusCodec,
325+
Codec<TaskInfo> taskInfoCodec,
326+
Codec<TaskInfo> taskInfoJsonCodec,
327+
Codec<TaskUpdateRequest> taskUpdateRequestCodec,
328+
Codec<PlanFragment> planFragmentCodec,
329+
Codec<MetadataUpdates> metadataUpdatesCodec,
330+
NodeStatsTracker nodeStatsTracker,
331+
RemoteTaskStats stats,
332+
boolean binaryTransportEnabled,
333+
boolean thriftTransportEnabled,
334+
boolean taskInfoThriftTransportEnabled,
335+
Protocol thriftProtocol,
336+
TableWriteInfo tableWriteInfo,
337+
int maxTaskUpdateSizeInBytes,
338+
MetadataManager metadataManager,
339+
QueryManager queryManager,
340+
DecayCounter taskUpdateRequestSize,
341+
HandleResolver handleResolver,
342+
ConnectorTypeSerdeManager connectorTypeSerdeManager,
343+
SchedulerStatsTracker schedulerStatsTracker,
344+
HttpRemoteTaskFactory.SafeEventLoop taskEventLoop)
271345
{
272346
requireNonNull(session, "session is null");
273347
requireNonNull(taskId, "taskId is null");
@@ -389,7 +463,11 @@ public HttpRemoteTask(
389463
handleResolver,
390464
connectorTypeSerdeManager,
391465
thriftProtocol);
466+
}
392467

468+
// this is a separate method to ensure that the `this` reference is not leaked during construction
469+
private void initialize()
470+
{
393471
taskStatusFetcher.addStateChangeListener(newStatus -> {
394472
verify(taskEventLoop.inEventLoop());
395473

@@ -404,7 +482,7 @@ public HttpRemoteTask(
404482
});
405483

406484
updateTaskStats();
407-
taskEventLoop.execute(this::updateSplitQueueSpace);
485+
safeExecuteOnEventLoop(this::updateSplitQueueSpace);
408486
}
409487

410488
public PlanFragment getPlanFragment()
@@ -445,7 +523,7 @@ public URI getRemoteTaskLocation()
445523
@Override
446524
public void start()
447525
{
448-
taskEventLoop.execute(() -> {
526+
safeExecuteOnEventLoop(() -> {
449527
// to start we just need to trigger an update
450528
started = true;
451529
scheduleUpdate();
@@ -465,7 +543,7 @@ public void addSplits(Multimap<PlanNodeId, Split> splitsBySource)
465543
return;
466544
}
467545

468-
taskEventLoop.execute(() -> {
546+
safeExecuteOnEventLoop(() -> {
469547
boolean updateNeeded = false;
470548
for (Entry<PlanNodeId, Collection<Split>> entry : splitsBySource.asMap().entrySet()) {
471549
PlanNodeId sourceId = entry.getKey();
@@ -502,7 +580,7 @@ public void addSplits(Multimap<PlanNodeId, Split> splitsBySource)
502580
@Override
503581
public void noMoreSplits(PlanNodeId sourceId)
504582
{
505-
taskEventLoop.execute(() -> {
583+
safeExecuteOnEventLoop(() -> {
506584
if (noMoreSplits.containsKey(sourceId)) {
507585
return;
508586
}
@@ -516,7 +594,7 @@ public void noMoreSplits(PlanNodeId sourceId)
516594
@Override
517595
public void noMoreSplits(PlanNodeId sourceId, Lifespan lifespan)
518596
{
519-
taskEventLoop.execute(() -> {
597+
safeExecuteOnEventLoop(() -> {
520598
if (pendingNoMoreSplitsForLifespan.put(sourceId, lifespan)) {
521599
needsUpdate = true;
522600
scheduleUpdate();
@@ -531,7 +609,7 @@ public void setOutputBuffers(OutputBuffers newOutputBuffers)
531609
return;
532610
}
533611

534-
taskEventLoop.execute(() -> {
612+
safeExecuteOnEventLoop(() -> {
535613
if (newOutputBuffers.getVersion() > outputBuffers.getVersion()) {
536614
outputBuffers = newOutputBuffers;
537615
needsUpdate = true;
@@ -696,7 +774,7 @@ public ListenableFuture<?> whenSplitQueueHasSpace(long weightThreshold)
696774
return immediateFuture(null);
697775
}
698776
SettableFuture<?> future = SettableFuture.create();
699-
taskEventLoop.execute(() -> {
777+
safeExecuteOnEventLoop(() -> {
700778
if (whenSplitQueueHasSpaceThreshold.isPresent()) {
701779
checkArgument(weightThreshold == whenSplitQueueHasSpaceThreshold.getAsLong(), "Multiple split queue space notification thresholds not supported");
702780
}
@@ -866,7 +944,7 @@ private void scheduleUpdate()
866944

867945
private void sendUpdate()
868946
{
869-
taskEventLoop.execute(() -> {
947+
safeExecuteOnEventLoop(() -> {
870948
TaskStatus taskStatus = getTaskStatus();
871949
// don't update if the task hasn't been started yet or if it is already finished
872950
if (!started || !needsUpdate || taskStatus.getState().isDone()) {
@@ -987,7 +1065,7 @@ private TaskSource getSource(PlanNodeId planNodeId)
9871065
@Override
9881066
public void cancel()
9891067
{
990-
taskEventLoop.execute(() -> {
1068+
safeExecuteOnEventLoop(() -> {
9911069
TaskStatus taskStatus = getTaskStatus();
9921070
if (taskStatus.getState().isDone()) {
9931071
return;
@@ -1007,7 +1085,7 @@ public void cancel()
10071085

10081086
private void cleanUpTask()
10091087
{
1010-
taskEventLoop.execute(() -> {
1088+
safeExecuteOnEventLoop(() -> {
10111089
checkState(getTaskStatus().getState().isDone(), "attempt to clean up a task that is not done yet");
10121090

10131091
// clear pending splits to free memory
@@ -1055,7 +1133,7 @@ public void abort()
10551133

10561134
private void abort(TaskStatus status)
10571135
{
1058-
taskEventLoop.execute(() -> {
1136+
safeExecuteOnEventLoop(() -> {
10591137
checkState(status.getState().isDone(), "cannot abort task with an incomplete status");
10601138

10611139
taskStatusFetcher.updateTaskStatus(status);
@@ -1317,4 +1395,19 @@ public void onFailure(Throwable throwable)
13171395
onFailureTaskInfo(throwable, this.action, this.request, this.cleanupBackoff);
13181396
}
13191397
}
1398+
1399+
/***
1400+
* Wrap the task execution on event loop to fail the entire task on any failure.
1401+
*/
1402+
private void safeExecuteOnEventLoop(Runnable r)
1403+
{
1404+
taskEventLoop.execute(() -> {
1405+
try {
1406+
r.run();
1407+
}
1408+
catch (Throwable t) {
1409+
failTask(t);
1410+
}
1411+
});
1412+
}
13201413
}

presto-main/src/main/java/com/facebook/presto/server/remotetask/HttpRemoteTaskFactory.java

+47-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.facebook.airlift.json.Codec;
1818
import com.facebook.airlift.json.JsonCodec;
1919
import com.facebook.airlift.json.smile.SmileCodec;
20+
import com.facebook.airlift.log.Logger;
2021
import com.facebook.airlift.stats.DecayCounter;
2122
import com.facebook.airlift.stats.ExponentialDecay;
2223
import com.facebook.drift.codec.ThriftCodec;
@@ -49,20 +50,25 @@
4950
import com.google.common.collect.Multimap;
5051
import com.google.common.util.concurrent.ThreadFactoryBuilder;
5152
import io.airlift.units.Duration;
53+
import io.netty.channel.DefaultEventLoop;
5254
import io.netty.channel.DefaultEventLoopGroup;
55+
import io.netty.channel.EventLoop;
5356
import io.netty.channel.EventLoopGroup;
5457
import org.weakref.jmx.Managed;
5558

5659
import javax.annotation.PreDestroy;
5760
import javax.inject.Inject;
5861

62+
import java.util.concurrent.Executor;
63+
5964
import static com.facebook.presto.server.thrift.ThriftCodecWrapper.wrapThriftCodec;
6065
import static java.lang.Math.toIntExact;
6166
import static java.util.Objects.requireNonNull;
6267

6368
public class HttpRemoteTaskFactory
6469
implements RemoteTaskFactory
6570
{
71+
private static final Logger log = Logger.get(HttpRemoteTaskFactory.class);
6672
private final HttpClient httpClient;
6773
private final LocationFactory locationFactory;
6874
private final Codec<TaskStatus> taskStatusCodec;
@@ -89,7 +95,14 @@ public class HttpRemoteTaskFactory
8995
private final QueryManager queryManager;
9096
private final DecayCounter taskUpdateRequestSize;
9197
private final EventLoopGroup eventLoopGroup = new DefaultEventLoopGroup(Runtime.getRuntime().availableProcessors(),
92-
new ThreadFactoryBuilder().setNameFormat("task-event-loop-%s").setDaemon(true).build());
98+
new ThreadFactoryBuilder().setNameFormat("task-event-loop-%s").setDaemon(true).build())
99+
{
100+
@Override
101+
protected EventLoop newChild(Executor executor, Object... args)
102+
{
103+
return new SafeEventLoop(this, executor);
104+
}
105+
};
93106

94107
@Inject
95108
public HttpRemoteTaskFactory(
@@ -194,7 +207,7 @@ public RemoteTask createRemoteTask(
194207
TableWriteInfo tableWriteInfo,
195208
SchedulerStatsTracker schedulerStatsTracker)
196209
{
197-
return new HttpRemoteTask(
210+
return HttpRemoteTask.createHttpRemoteTask(
198211
session,
199212
taskId,
200213
node.getNodeIdentifier(),
@@ -229,6 +242,37 @@ public RemoteTask createRemoteTask(
229242
handleResolver,
230243
connectorTypeSerdeManager,
231244
schedulerStatsTracker,
232-
eventLoopGroup.next());
245+
(SafeEventLoop) eventLoopGroup.next());
246+
}
247+
248+
/***
249+
* One observation about event loop is if submitted task fails, it could kill the thread but the event loop group will not create a new one.
250+
* Here, we wrap it as safe event loop so that if any submitted job fails, we chose to log the error and fail the entire task.
251+
*/
252+
static class SafeEventLoop
253+
extends DefaultEventLoop
254+
{
255+
public SafeEventLoop(EventLoopGroup parent, Executor executor)
256+
{
257+
super(parent, executor);
258+
}
259+
260+
@Override
261+
protected void run()
262+
{
263+
do {
264+
Runnable task = takeTask();
265+
if (task != null) {
266+
try {
267+
runTask(task);
268+
}
269+
catch (Throwable t) {
270+
log.error("Error executing task on event loop", t);
271+
}
272+
updateLastExecutionTime();
273+
}
274+
}
275+
while (!this.confirmShutdown());
276+
}
233277
}
234278
}

0 commit comments

Comments
 (0)