embulk-output-multi 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/build.gradle +1 -1
- data/gradle.properties +1 -1
- data/gradle/dependency-locks/testCompileClasspath.lockfile +1 -1
- data/src/main/java/org/embulk/output/multi/ControlDelegate.java +40 -0
- data/src/main/java/org/embulk/output/multi/MultiOutputPlugin.java +27 -259
- data/src/main/java/org/embulk/output/multi/OutputPluginDelegate.java +111 -0
- data/src/main/java/org/embulk/output/multi/PluginExecutionException.java +15 -0
- data/src/main/java/org/embulk/output/multi/RunControlTask.java +65 -0
- data/src/main/java/org/embulk/output/multi/TaskReports.java +25 -0
- data/src/main/java/org/embulk/output/multi/TransactionalPageOutputDelegate.java +93 -0
- data/src/test/java/org/embulk/output/multi/TestMultiOutputPlugin.java +9 -12
- data/src/test/resources/yaml/{out_stop_on_failed_output.yml → out_failed_output_after_open.yml} +0 -0
- data/src/test/resources/yaml/out_failed_output_before_open.yml +13 -0
- metadata +11 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb52fdd126c8c9e752f186479dfdd1e8bb60560c
|
4
|
+
data.tar.gz: fd2f7fa9b9ee60bd2d7aaaa598b9ade60c611abf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4c8cd6915a54439599fc5a6f6373a6b72c2e678ecfa3ccf02d406a3342f2358b47bfe8b8dbe6661f74a59d622eda60fdd122bca2d5a9b8b95d0eb2d3d4cf6964
|
7
|
+
data.tar.gz: 16179171d941d670370d374cda08d683fa36e4553422b313b0c1e1db4c6649525c66ef6a466a7a23505cac98c9a086f074ec5ca153d233e34c906e322da51d00
|
data/build.gradle
CHANGED
@@ -19,7 +19,7 @@ configurations {
|
|
19
19
|
dependencies {
|
20
20
|
testImplementation "org.embulk:embulk-standards:0.9.15"
|
21
21
|
testImplementation "org.embulk:embulk-test:0.9.15"
|
22
|
-
testImplementation "com.github.kamatama41:embulk-test-helpers:0.7.
|
22
|
+
testImplementation "com.github.kamatama41:embulk-test-helpers:0.7.4"
|
23
23
|
testImplementation "org.junit.jupiter:junit-jupiter-api:5.3.2"
|
24
24
|
testImplementation "org.junit.jupiter:junit-jupiter-engine:5.3.2"
|
25
25
|
}
|
data/gradle.properties
CHANGED
@@ -1 +1 @@
|
|
1
|
-
version=0.
|
1
|
+
version=0.2.0
|
@@ -9,7 +9,7 @@ com.fasterxml.jackson.datatype:jackson-datatype-guava:2.6.7
|
|
9
9
|
com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.6.7
|
10
10
|
com.fasterxml.jackson.datatype:jackson-datatype-joda:2.6.7
|
11
11
|
com.fasterxml.jackson.module:jackson-module-guice:2.6.7
|
12
|
-
com.github.kamatama41:embulk-test-helpers:0.7.
|
12
|
+
com.github.kamatama41:embulk-test-helpers:0.7.4
|
13
13
|
com.google.code.findbugs:annotations:3.0.0
|
14
14
|
com.google.guava:guava:18.0
|
15
15
|
com.google.inject.extensions:guice-multibindings:4.0
|
@@ -0,0 +1,40 @@
|
|
1
|
+
package org.embulk.output.multi;
|
2
|
+
|
3
|
+
import org.embulk.config.TaskReport;
|
4
|
+
import org.embulk.config.TaskSource;
|
5
|
+
import org.embulk.spi.OutputPlugin;
|
6
|
+
|
7
|
+
import java.util.ArrayList;
|
8
|
+
import java.util.List;
|
9
|
+
import java.util.concurrent.ExecutionException;
|
10
|
+
|
11
|
+
class ControlDelegate implements OutputPlugin.Control {
|
12
|
+
private final int pluginIndex;
|
13
|
+
private final RunControlTask controlTask;
|
14
|
+
|
15
|
+
ControlDelegate(int index, RunControlTask controlTask) {
|
16
|
+
this.pluginIndex = index;
|
17
|
+
this.controlTask = controlTask;
|
18
|
+
}
|
19
|
+
|
20
|
+
@Override
|
21
|
+
public List<TaskReport> run(TaskSource taskSource) {
|
22
|
+
controlTask.addTaskSource(pluginIndex, taskSource);
|
23
|
+
List<TaskReport> reports;
|
24
|
+
try {
|
25
|
+
reports = controlTask.waitAndGetResult();
|
26
|
+
} catch (InterruptedException e) {
|
27
|
+
Thread.currentThread().interrupt();
|
28
|
+
throw new RuntimeException(e);
|
29
|
+
} catch (ExecutionException e) {
|
30
|
+
throw new RuntimeException(e.getCause());
|
31
|
+
}
|
32
|
+
|
33
|
+
final List<TaskReport> result = new ArrayList<>();
|
34
|
+
for (TaskReport taskReport : reports) {
|
35
|
+
final TaskReport report = taskReport.get(TaskReports.class, MultiOutputPlugin.CONFIG_NAME_OUTPUT_TASK_REPORTS).get(pluginIndex);
|
36
|
+
result.add(report);
|
37
|
+
}
|
38
|
+
return result;
|
39
|
+
}
|
40
|
+
}
|
@@ -1,7 +1,5 @@
|
|
1
1
|
package org.embulk.output.multi;
|
2
2
|
|
3
|
-
import com.fasterxml.jackson.annotation.JsonCreator;
|
4
|
-
import com.fasterxml.jackson.annotation.JsonProperty;
|
5
3
|
import org.embulk.config.Config;
|
6
4
|
import org.embulk.config.ConfigDefault;
|
7
5
|
import org.embulk.config.ConfigDiff;
|
@@ -18,31 +16,20 @@ import org.embulk.spi.OutputPlugin;
|
|
18
16
|
import org.embulk.spi.Page;
|
19
17
|
import org.embulk.spi.Schema;
|
20
18
|
import org.embulk.spi.TransactionalPageOutput;
|
21
|
-
import org.slf4j.Logger;
|
22
|
-
import org.slf4j.LoggerFactory;
|
23
19
|
|
24
20
|
import java.util.ArrayList;
|
25
|
-
import java.util.Arrays;
|
26
21
|
import java.util.List;
|
27
22
|
import java.util.Optional;
|
28
|
-
import java.util.concurrent.Callable;
|
29
|
-
import java.util.concurrent.CancellationException;
|
30
|
-
import java.util.concurrent.CountDownLatch;
|
31
23
|
import java.util.concurrent.ExecutionException;
|
32
|
-
import java.util.concurrent.ExecutorService;
|
33
|
-
import java.util.concurrent.Executors;
|
34
24
|
import java.util.concurrent.Future;
|
35
25
|
import java.util.function.Function;
|
26
|
+
import java.util.stream.Collectors;
|
36
27
|
|
37
28
|
public class MultiOutputPlugin implements OutputPlugin {
|
38
29
|
public interface PluginTask extends Task {
|
39
30
|
@Config("outputs")
|
40
31
|
List<ConfigSource> getOutputConfigs();
|
41
32
|
|
42
|
-
@Config("stop_on_failed_output")
|
43
|
-
@ConfigDefault("false")
|
44
|
-
boolean getStopOnFailedOutput();
|
45
|
-
|
46
33
|
@Config(CONFIG_NAME_OUTPUT_CONFIG_DIFFS)
|
47
34
|
@ConfigDefault("null")
|
48
35
|
Optional<List<ConfigDiff>> getOutputConfigDiffs();
|
@@ -52,13 +39,7 @@ public class MultiOutputPlugin implements OutputPlugin {
|
|
52
39
|
}
|
53
40
|
|
54
41
|
private static final String CONFIG_NAME_OUTPUT_CONFIG_DIFFS = "output_config_diffs";
|
55
|
-
|
56
|
-
private static final Logger LOGGER = LoggerFactory.getLogger(MultiOutputPlugin.class);
|
57
|
-
private final ExecutorService executorService;
|
58
|
-
|
59
|
-
public MultiOutputPlugin() {
|
60
|
-
this.executorService = Executors.newCachedThreadPool();
|
61
|
-
}
|
42
|
+
static final String CONFIG_NAME_OUTPUT_TASK_REPORTS = "output_task_reports";
|
62
43
|
|
63
44
|
@Override
|
64
45
|
public ConfigDiff transaction(ConfigSource config, Schema schema, int taskCount, OutputPlugin.Control control) {
|
@@ -67,8 +48,7 @@ public class MultiOutputPlugin implements OutputPlugin {
|
|
67
48
|
throw new ConfigException("'outputs' must have more than than or equals to 1 element.");
|
68
49
|
}
|
69
50
|
final ExecSession session = Exec.session();
|
70
|
-
final RunControlTask controlTask = new RunControlTask(task, control
|
71
|
-
controlTask.runAsynchronously();
|
51
|
+
final RunControlTask controlTask = new RunControlTask(task, control);
|
72
52
|
return buildConfigDiff(mapWithPluginDelegate(task, session, delegate ->
|
73
53
|
delegate.transaction(schema, taskCount, controlTask)
|
74
54
|
));
|
@@ -78,8 +58,7 @@ public class MultiOutputPlugin implements OutputPlugin {
|
|
78
58
|
public ConfigDiff resume(TaskSource taskSource, Schema schema, int taskCount, OutputPlugin.Control control) {
|
79
59
|
final PluginTask task = taskSource.loadTask(PluginTask.class);
|
80
60
|
final ExecSession session = Exec.session();
|
81
|
-
final RunControlTask controlTask = new RunControlTask(task, control
|
82
|
-
controlTask.runAsynchronously();
|
61
|
+
final RunControlTask controlTask = new RunControlTask(task, control);
|
83
62
|
return buildConfigDiff(mapWithPluginDelegate(task, session, delegate ->
|
84
63
|
delegate.resume(schema, taskCount, controlTask)
|
85
64
|
));
|
@@ -100,14 +79,14 @@ public class MultiOutputPlugin implements OutputPlugin {
|
|
100
79
|
final PluginTask task = taskSource.loadTask(PluginTask.class);
|
101
80
|
final ExecSession session = Exec.session();
|
102
81
|
final List<TransactionalPageOutputDelegate> delegates = mapWithPluginDelegate(task, session, delegate ->
|
103
|
-
new TransactionalPageOutputDelegate(delegate, delegate.open(schema, taskIndex)
|
82
|
+
new TransactionalPageOutputDelegate(taskIndex, delegate, delegate.open(schema, taskIndex))
|
104
83
|
);
|
105
84
|
|
106
85
|
return new TransactionalPageOutput() {
|
107
86
|
@Override
|
108
87
|
public void add(Page original) {
|
109
88
|
final Buffer originalBuffer = original.buffer();
|
110
|
-
for (
|
89
|
+
for (TransactionalPageOutputDelegate output : delegates) {
|
111
90
|
final Buffer copiedBuffer = Buffer.wrap(originalBuffer.array());
|
112
91
|
copiedBuffer.offset(originalBuffer.offset());
|
113
92
|
copiedBuffer.limit(originalBuffer.limit());
|
@@ -122,21 +101,21 @@ public class MultiOutputPlugin implements OutputPlugin {
|
|
122
101
|
|
123
102
|
@Override
|
124
103
|
public void finish() {
|
125
|
-
for (
|
104
|
+
for (TransactionalPageOutputDelegate output : delegates) {
|
126
105
|
output.finish();
|
127
106
|
}
|
128
107
|
}
|
129
108
|
|
130
109
|
@Override
|
131
110
|
public void close() {
|
132
|
-
for (
|
111
|
+
for (TransactionalPageOutputDelegate output : delegates) {
|
133
112
|
output.close();
|
134
113
|
}
|
135
114
|
}
|
136
115
|
|
137
116
|
@Override
|
138
117
|
public void abort() {
|
139
|
-
for (
|
118
|
+
for (TransactionalPageOutputDelegate output : delegates) {
|
140
119
|
output.abort();
|
141
120
|
}
|
142
121
|
}
|
@@ -145,8 +124,19 @@ public class MultiOutputPlugin implements OutputPlugin {
|
|
145
124
|
public TaskReport commit() {
|
146
125
|
final TaskReport report = Exec.newTaskReport();
|
147
126
|
final List<TaskReport> reports = new ArrayList<>();
|
148
|
-
|
149
|
-
|
127
|
+
final List<OutputPluginDelegate> errorPlugins = new ArrayList<>();
|
128
|
+
for (TransactionalPageOutputDelegate output : delegates) {
|
129
|
+
try {
|
130
|
+
reports.add(output.commit());
|
131
|
+
} catch (PluginExecutionException e) {
|
132
|
+
errorPlugins.add(e.getPlugin());
|
133
|
+
}
|
134
|
+
}
|
135
|
+
if (!errorPlugins.isEmpty()) {
|
136
|
+
throw new RuntimeException(
|
137
|
+
String.format("Following plugins failed to output [%s]",
|
138
|
+
errorPlugins.stream().map(OutputPluginDelegate::getPluginCode).collect(Collectors.joining(", "))
|
139
|
+
));
|
150
140
|
}
|
151
141
|
report.set(CONFIG_NAME_OUTPUT_TASK_REPORTS, new TaskReports(reports));
|
152
142
|
return report;
|
@@ -154,239 +144,17 @@ public class MultiOutputPlugin implements OutputPlugin {
|
|
154
144
|
};
|
155
145
|
}
|
156
146
|
|
157
|
-
private static class RunControlTask implements Callable<List<TaskReport>> {
|
158
|
-
private final PluginTask task;
|
159
|
-
private final OutputPlugin.Control control;
|
160
|
-
private final ExecutorService executorService;
|
161
|
-
private final CountDownLatch latch;
|
162
|
-
private final TaskSource[] taskSources;
|
163
|
-
private Future<List<TaskReport>> future;
|
164
|
-
|
165
|
-
RunControlTask(PluginTask task, OutputPlugin.Control control, ExecutorService executorService) {
|
166
|
-
this.task = task;
|
167
|
-
this.control = control;
|
168
|
-
this.executorService = executorService;
|
169
|
-
this.latch = new CountDownLatch(task.getOutputConfigs().size());
|
170
|
-
this.taskSources = new TaskSource[task.getOutputConfigs().size()];
|
171
|
-
}
|
172
|
-
|
173
|
-
@Override
|
174
|
-
public List<TaskReport> call() throws Exception {
|
175
|
-
latch.await();
|
176
|
-
task.setTaskSources(Arrays.asList(taskSources));
|
177
|
-
return control.run(task.dump());
|
178
|
-
}
|
179
|
-
|
180
|
-
void runAsynchronously() {
|
181
|
-
future = executorService.submit(this);
|
182
|
-
}
|
183
|
-
|
184
|
-
void cancel() {
|
185
|
-
future.cancel(true);
|
186
|
-
}
|
187
|
-
|
188
|
-
void addTaskSource(int index, TaskSource taskSource) {
|
189
|
-
taskSources[index] = taskSource;
|
190
|
-
latch.countDown();
|
191
|
-
}
|
192
|
-
|
193
|
-
List<TaskReport> waitAndGetResult() throws ExecutionException, InterruptedException {
|
194
|
-
return future.get();
|
195
|
-
}
|
196
|
-
}
|
197
|
-
|
198
|
-
private static class OutputPluginDelegate {
|
199
|
-
private final int pluginIndex;
|
200
|
-
private final PluginType type;
|
201
|
-
private final OutputPlugin plugin;
|
202
|
-
private final ConfigSource config;
|
203
|
-
private final TaskSource taskSource;
|
204
|
-
private final ExecutorService executorService;
|
205
|
-
|
206
|
-
OutputPluginDelegate(
|
207
|
-
int pluginIndex,
|
208
|
-
PluginType type,
|
209
|
-
OutputPlugin plugin,
|
210
|
-
ConfigSource config,
|
211
|
-
TaskSource taskSource,
|
212
|
-
ExecutorService executorService
|
213
|
-
) {
|
214
|
-
this.pluginIndex = pluginIndex;
|
215
|
-
this.type = type;
|
216
|
-
this.plugin = plugin;
|
217
|
-
this.config = config;
|
218
|
-
this.taskSource = taskSource;
|
219
|
-
this.executorService = executorService;
|
220
|
-
}
|
221
|
-
|
222
|
-
Future<ConfigDiff> transaction(Schema schema, int taskCount, RunControlTask controlTask) {
|
223
|
-
LOGGER.debug("Run transaction for {}", getPluginNameForLogging());
|
224
|
-
return executorService.submit(() -> {
|
225
|
-
try {
|
226
|
-
return plugin.transaction(config, schema, taskCount, new ControlDelegate(pluginIndex, controlTask));
|
227
|
-
} catch (CancellationException e) {
|
228
|
-
LOGGER.error("Canceled transaction for {} by other plugin's error", getPluginNameForLogging());
|
229
|
-
throw e;
|
230
|
-
} catch (Exception e) {
|
231
|
-
LOGGER.error("Transaction for {} failed.", getPluginNameForLogging(), e);
|
232
|
-
controlTask.cancel();
|
233
|
-
throw e;
|
234
|
-
}
|
235
|
-
});
|
236
|
-
}
|
237
|
-
|
238
|
-
Future<ConfigDiff> resume(Schema schema, int taskCount, RunControlTask controlTask) {
|
239
|
-
LOGGER.debug("Run resume for {}", getPluginNameForLogging());
|
240
|
-
return executorService.submit(() -> {
|
241
|
-
try {
|
242
|
-
return plugin.resume(taskSource, schema, taskCount, new ControlDelegate(pluginIndex, controlTask));
|
243
|
-
} catch (CancellationException e) {
|
244
|
-
LOGGER.error("Canceled resume for {} by other plugin's error", getPluginNameForLogging());
|
245
|
-
throw e;
|
246
|
-
} catch (Exception e) {
|
247
|
-
LOGGER.error("Resume for {} failed.", getPluginNameForLogging(), e);
|
248
|
-
controlTask.cancel();
|
249
|
-
throw e;
|
250
|
-
}
|
251
|
-
});
|
252
|
-
}
|
253
|
-
|
254
|
-
void cleanup(Schema schema, int taskCount, List<TaskReport> successTaskReports) {
|
255
|
-
LOGGER.debug("Run cleanup for {}", getPluginNameForLogging());
|
256
|
-
List<TaskReport> successReportsForPlugin = new ArrayList<>();
|
257
|
-
for (TaskReport successTaskReport : successTaskReports) {
|
258
|
-
final TaskReport report = successTaskReport.get(TaskReports.class, CONFIG_NAME_OUTPUT_TASK_REPORTS).get(pluginIndex);
|
259
|
-
successReportsForPlugin.add(report);
|
260
|
-
plugin.cleanup(taskSource, schema, taskCount, successReportsForPlugin);
|
261
|
-
}
|
262
|
-
}
|
263
|
-
|
264
|
-
TransactionalPageOutput open(Schema schema, int taskIndex) {
|
265
|
-
LOGGER.debug("Run open for {}", getPluginNameForLogging());
|
266
|
-
return plugin.open(taskSource, schema, taskIndex);
|
267
|
-
}
|
268
|
-
|
269
|
-
private String getPluginNameForLogging() {
|
270
|
-
return String.format("%s output plugin (pluginIndex: %s)", type.getName(), pluginIndex);
|
271
|
-
}
|
272
|
-
}
|
273
|
-
|
274
|
-
private static class TransactionalPageOutputDelegate implements TransactionalPageOutput {
|
275
|
-
private final OutputPluginDelegate source;
|
276
|
-
private final TransactionalPageOutput delegate;
|
277
|
-
private final boolean isStopOnFailedOutput;
|
278
|
-
|
279
|
-
TransactionalPageOutputDelegate(
|
280
|
-
OutputPluginDelegate source, TransactionalPageOutput delegate, boolean isStopOnFailedOutput) {
|
281
|
-
this.source = source;
|
282
|
-
this.delegate = delegate;
|
283
|
-
this.isStopOnFailedOutput = isStopOnFailedOutput;
|
284
|
-
}
|
285
|
-
|
286
|
-
@Override
|
287
|
-
public void add(Page page) {
|
288
|
-
try {
|
289
|
-
delegate.add(page);
|
290
|
-
} catch (Exception e) {
|
291
|
-
warnOrException(e);
|
292
|
-
}
|
293
|
-
}
|
294
|
-
|
295
|
-
@Override
|
296
|
-
public void finish() {
|
297
|
-
try {
|
298
|
-
delegate.finish();
|
299
|
-
} catch (Exception e) {
|
300
|
-
warnOrException(e);
|
301
|
-
}
|
302
|
-
}
|
303
|
-
|
304
|
-
@Override
|
305
|
-
public void close() {
|
306
|
-
try {
|
307
|
-
delegate.close();
|
308
|
-
} catch (Exception e) {
|
309
|
-
warnOrException(e);
|
310
|
-
}
|
311
|
-
}
|
312
|
-
|
313
|
-
@Override
|
314
|
-
public void abort() {
|
315
|
-
try {
|
316
|
-
delegate.abort();
|
317
|
-
} catch (Exception e) {
|
318
|
-
warnOrException(e);
|
319
|
-
}
|
320
|
-
}
|
321
|
-
|
322
|
-
@Override
|
323
|
-
public TaskReport commit() {
|
324
|
-
return delegate.commit();
|
325
|
-
}
|
326
|
-
|
327
|
-
private void warnOrException(Exception e) {
|
328
|
-
final String message = String.format("Failed on output for %s.", source.getPluginNameForLogging());
|
329
|
-
if (isStopOnFailedOutput) {
|
330
|
-
throw new RuntimeException(message, e);
|
331
|
-
}
|
332
|
-
LOGGER.warn(message);
|
333
|
-
}
|
334
|
-
}
|
335
|
-
|
336
|
-
private static class ControlDelegate implements OutputPlugin.Control {
|
337
|
-
private final int pluginIndex;
|
338
|
-
private final RunControlTask controlTask;
|
339
|
-
|
340
|
-
ControlDelegate(int index, RunControlTask controlTask) {
|
341
|
-
this.pluginIndex = index;
|
342
|
-
this.controlTask = controlTask;
|
343
|
-
}
|
344
|
-
|
345
|
-
@Override
|
346
|
-
public List<TaskReport> run(TaskSource taskSource) {
|
347
|
-
controlTask.addTaskSource(pluginIndex, taskSource);
|
348
|
-
List<TaskReport> reports;
|
349
|
-
try {
|
350
|
-
reports = controlTask.waitAndGetResult();
|
351
|
-
} catch (InterruptedException | ExecutionException e) {
|
352
|
-
throw new RuntimeException(e);
|
353
|
-
}
|
354
|
-
|
355
|
-
final List<TaskReport> result = new ArrayList<>();
|
356
|
-
for (TaskReport taskReport : reports) {
|
357
|
-
final TaskReport report = taskReport.get(TaskReports.class, CONFIG_NAME_OUTPUT_TASK_REPORTS).get(pluginIndex);
|
358
|
-
result.add(report);
|
359
|
-
}
|
360
|
-
return result;
|
361
|
-
}
|
362
|
-
}
|
363
|
-
|
364
|
-
private static class TaskReports {
|
365
|
-
private final List<TaskReport> reports;
|
366
|
-
|
367
|
-
@JsonCreator
|
368
|
-
TaskReports(@JsonProperty("reports") List<TaskReport> reports) {
|
369
|
-
this.reports = reports;
|
370
|
-
}
|
371
|
-
|
372
|
-
@JsonProperty("reports")
|
373
|
-
List<TaskReport> getReports() {
|
374
|
-
return reports;
|
375
|
-
}
|
376
|
-
|
377
|
-
TaskReport get(int index) {
|
378
|
-
return reports.get(index);
|
379
|
-
}
|
380
|
-
}
|
381
|
-
|
382
147
|
private static ConfigDiff buildConfigDiff(List<Future<ConfigDiff>> runPluginTasks) {
|
383
148
|
final ConfigDiff configDiff = Exec.newConfigDiff();
|
384
149
|
List<ConfigDiff> configDiffs = new ArrayList<>();
|
385
150
|
for (Future<ConfigDiff> pluginTask : runPluginTasks) {
|
386
151
|
try {
|
387
152
|
configDiffs.add(pluginTask.get());
|
388
|
-
} catch (InterruptedException
|
153
|
+
} catch (InterruptedException e) {
|
154
|
+
Thread.currentThread().interrupt();
|
389
155
|
throw new RuntimeException(e);
|
156
|
+
} catch (ExecutionException e) {
|
157
|
+
throw new RuntimeException(e.getCause());
|
390
158
|
}
|
391
159
|
}
|
392
160
|
configDiff.set(CONFIG_NAME_OUTPUT_CONFIG_DIFFS, configDiffs);
|
@@ -406,7 +174,7 @@ public class MultiOutputPlugin implements OutputPlugin {
|
|
406
174
|
if (task.getTaskSources() != null) {
|
407
175
|
taskSource = task.getTaskSources().get(pluginIndex);
|
408
176
|
}
|
409
|
-
result.add(action.apply(new OutputPluginDelegate(pluginIndex, pluginType, outputPlugin, config, taskSource
|
177
|
+
result.add(action.apply(new OutputPluginDelegate(pluginIndex, pluginType, outputPlugin, config, taskSource)));
|
410
178
|
}
|
411
179
|
return result;
|
412
180
|
}
|
@@ -0,0 +1,111 @@
|
|
1
|
+
package org.embulk.output.multi;
|
2
|
+
|
3
|
+
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
4
|
+
import org.embulk.config.ConfigDiff;
|
5
|
+
import org.embulk.config.ConfigSource;
|
6
|
+
import org.embulk.config.TaskReport;
|
7
|
+
import org.embulk.config.TaskSource;
|
8
|
+
import org.embulk.plugin.PluginType;
|
9
|
+
import org.embulk.spi.OutputPlugin;
|
10
|
+
import org.embulk.spi.Schema;
|
11
|
+
import org.embulk.spi.TransactionalPageOutput;
|
12
|
+
import org.slf4j.Logger;
|
13
|
+
import org.slf4j.LoggerFactory;
|
14
|
+
|
15
|
+
import java.util.ArrayList;
|
16
|
+
import java.util.List;
|
17
|
+
import java.util.concurrent.CancellationException;
|
18
|
+
import java.util.concurrent.ExecutorService;
|
19
|
+
import java.util.concurrent.Executors;
|
20
|
+
import java.util.concurrent.Future;
|
21
|
+
|
22
|
+
class OutputPluginDelegate {
|
23
|
+
private static final Logger LOGGER = LoggerFactory.getLogger(OutputPluginDelegate.class);
|
24
|
+
private static final String THREAD_NAME_FORMAT = "multi-output-plugin-%s-%%d";
|
25
|
+
private final int pluginIndex;
|
26
|
+
private final PluginType type;
|
27
|
+
private final OutputPlugin plugin;
|
28
|
+
private final ConfigSource config;
|
29
|
+
private final TaskSource taskSource;
|
30
|
+
private final ExecutorService executorService;
|
31
|
+
|
32
|
+
OutputPluginDelegate(
|
33
|
+
int pluginIndex,
|
34
|
+
PluginType type,
|
35
|
+
OutputPlugin plugin,
|
36
|
+
ConfigSource config,
|
37
|
+
TaskSource taskSource
|
38
|
+
) {
|
39
|
+
this.pluginIndex = pluginIndex;
|
40
|
+
this.type = type;
|
41
|
+
this.plugin = plugin;
|
42
|
+
this.config = config;
|
43
|
+
this.taskSource = taskSource;
|
44
|
+
this.executorService = Executors.newSingleThreadExecutor(
|
45
|
+
new ThreadFactoryBuilder().setNameFormat(String.format(THREAD_NAME_FORMAT, generatePluginCode(type, pluginIndex))).build()
|
46
|
+
);
|
47
|
+
}
|
48
|
+
|
49
|
+
Future<ConfigDiff> transaction(Schema schema, int taskCount, RunControlTask controlTask) {
|
50
|
+
return executorService.submit(() -> {
|
51
|
+
try {
|
52
|
+
LOGGER.debug("Run #transaction for {}", getPluginCode());
|
53
|
+
return plugin.transaction(config, schema, taskCount, new ControlDelegate(pluginIndex, controlTask));
|
54
|
+
} catch (CancellationException e) {
|
55
|
+
LOGGER.error("Canceled #transaction for {} by other plugin's error", getPluginCode());
|
56
|
+
throw e;
|
57
|
+
} catch (Exception e) {
|
58
|
+
LOGGER.error("Transaction for {} failed.", getPluginCode(), e);
|
59
|
+
controlTask.cancel();
|
60
|
+
throw e;
|
61
|
+
} finally {
|
62
|
+
executorService.shutdown();
|
63
|
+
}
|
64
|
+
});
|
65
|
+
}
|
66
|
+
|
67
|
+
Future<ConfigDiff> resume(Schema schema, int taskCount, RunControlTask controlTask) {
|
68
|
+
return executorService.submit(() -> {
|
69
|
+
try {
|
70
|
+
LOGGER.debug("Run #resume for {}", getPluginCode());
|
71
|
+
return plugin.resume(taskSource, schema, taskCount, new ControlDelegate(pluginIndex, controlTask));
|
72
|
+
} catch (CancellationException e) {
|
73
|
+
LOGGER.error("Canceled #resume for {} by other plugin's error", getPluginCode());
|
74
|
+
throw e;
|
75
|
+
} catch (Exception e) {
|
76
|
+
LOGGER.error("Resume for {} failed.", getPluginCode(), e);
|
77
|
+
controlTask.cancel();
|
78
|
+
throw e;
|
79
|
+
} finally {
|
80
|
+
executorService.shutdown();
|
81
|
+
}
|
82
|
+
});
|
83
|
+
}
|
84
|
+
|
85
|
+
void cleanup(Schema schema, int taskCount, List<TaskReport> successTaskReports) {
|
86
|
+
LOGGER.debug("Run #cleanup for {}", getPluginCode());
|
87
|
+
List<TaskReport> successReportsForPlugin = new ArrayList<>();
|
88
|
+
for (TaskReport successTaskReport : successTaskReports) {
|
89
|
+
final TaskReport report = successTaskReport.get(TaskReports.class, MultiOutputPlugin.CONFIG_NAME_OUTPUT_TASK_REPORTS).get(pluginIndex);
|
90
|
+
successReportsForPlugin.add(report);
|
91
|
+
}
|
92
|
+
plugin.cleanup(taskSource, schema, taskCount, successReportsForPlugin);
|
93
|
+
}
|
94
|
+
|
95
|
+
TransactionalPageOutput open(Schema schema, int taskIndex) {
|
96
|
+
LOGGER.debug("Run #open for {}", getPluginCode());
|
97
|
+
return plugin.open(taskSource, schema, taskIndex);
|
98
|
+
}
|
99
|
+
|
100
|
+
PluginType getType() {
|
101
|
+
return type;
|
102
|
+
}
|
103
|
+
|
104
|
+
String getPluginCode() {
|
105
|
+
return generatePluginCode(type, pluginIndex);
|
106
|
+
}
|
107
|
+
|
108
|
+
private static String generatePluginCode(PluginType type, int pluginIndex) {
|
109
|
+
return String.format("%s-%d", type.getName(), pluginIndex);
|
110
|
+
}
|
111
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
package org.embulk.output.multi;
|
2
|
+
|
3
|
+
class PluginExecutionException extends RuntimeException {
|
4
|
+
|
5
|
+
private final OutputPluginDelegate plugin;
|
6
|
+
|
7
|
+
PluginExecutionException(OutputPluginDelegate plugin, Throwable cause) {
|
8
|
+
super(cause);
|
9
|
+
this.plugin = plugin;
|
10
|
+
}
|
11
|
+
|
12
|
+
OutputPluginDelegate getPlugin() {
|
13
|
+
return plugin;
|
14
|
+
}
|
15
|
+
}
|
@@ -0,0 +1,65 @@
|
|
1
|
+
package org.embulk.output.multi;
|
2
|
+
|
3
|
+
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
4
|
+
import org.embulk.config.TaskReport;
|
5
|
+
import org.embulk.config.TaskSource;
|
6
|
+
import org.embulk.spi.OutputPlugin;
|
7
|
+
|
8
|
+
import java.util.ArrayList;
|
9
|
+
import java.util.List;
|
10
|
+
import java.util.concurrent.Callable;
|
11
|
+
import java.util.concurrent.CountDownLatch;
|
12
|
+
import java.util.concurrent.ExecutionException;
|
13
|
+
import java.util.concurrent.ExecutorService;
|
14
|
+
import java.util.concurrent.Executors;
|
15
|
+
import java.util.concurrent.Future;
|
16
|
+
import java.util.concurrent.atomic.AtomicReferenceArray;
|
17
|
+
|
18
|
+
class RunControlTask implements Callable<List<TaskReport>> {
|
19
|
+
private static final String THREAD_NAME_FORMAT = "multi-run-control-%d";
|
20
|
+
private final MultiOutputPlugin.PluginTask task;
|
21
|
+
private final OutputPlugin.Control control;
|
22
|
+
private final CountDownLatch latch;
|
23
|
+
private final AtomicReferenceArray<TaskSource> taskSources;
|
24
|
+
private final ExecutorService executorService;
|
25
|
+
private Future<List<TaskReport>> result;
|
26
|
+
|
27
|
+
RunControlTask(MultiOutputPlugin.PluginTask task, OutputPlugin.Control control) {
|
28
|
+
this.task = task;
|
29
|
+
this.control = control;
|
30
|
+
this.latch = new CountDownLatch(task.getOutputConfigs().size());
|
31
|
+
this.taskSources = new AtomicReferenceArray<>(task.getOutputConfigs().size());
|
32
|
+
this.executorService = Executors.newSingleThreadExecutor(
|
33
|
+
new ThreadFactoryBuilder().setNameFormat(THREAD_NAME_FORMAT).build()
|
34
|
+
);
|
35
|
+
this.result = executorService.submit(this);
|
36
|
+
}
|
37
|
+
|
38
|
+
@Override
|
39
|
+
public List<TaskReport> call() throws Exception {
|
40
|
+
latch.await();
|
41
|
+
List<TaskSource> taskSources = new ArrayList<>(this.taskSources.length());
|
42
|
+
for (int i = 0; i < this.taskSources.length(); i++) {
|
43
|
+
taskSources.add(this.taskSources.get(i));
|
44
|
+
}
|
45
|
+
task.setTaskSources(taskSources);
|
46
|
+
return control.run(task.dump());
|
47
|
+
}
|
48
|
+
|
49
|
+
void cancel() {
|
50
|
+
result.cancel(true);
|
51
|
+
}
|
52
|
+
|
53
|
+
void addTaskSource(int index, TaskSource taskSource) {
|
54
|
+
taskSources.set(index, taskSource);
|
55
|
+
latch.countDown();
|
56
|
+
}
|
57
|
+
|
58
|
+
List<TaskReport> waitAndGetResult() throws ExecutionException, InterruptedException {
|
59
|
+
try {
|
60
|
+
return result.get();
|
61
|
+
} finally {
|
62
|
+
executorService.shutdown();
|
63
|
+
}
|
64
|
+
}
|
65
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
package org.embulk.output.multi;
|
2
|
+
|
3
|
+
import com.fasterxml.jackson.annotation.JsonCreator;
|
4
|
+
import com.fasterxml.jackson.annotation.JsonProperty;
|
5
|
+
import org.embulk.config.TaskReport;
|
6
|
+
|
7
|
+
import java.util.List;
|
8
|
+
|
9
|
+
class TaskReports {
|
10
|
+
private final List<TaskReport> reports;
|
11
|
+
|
12
|
+
@JsonCreator
|
13
|
+
TaskReports(@JsonProperty("reports") List<TaskReport> reports) {
|
14
|
+
this.reports = reports;
|
15
|
+
}
|
16
|
+
|
17
|
+
@JsonProperty("reports")
|
18
|
+
List<TaskReport> getReports() {
|
19
|
+
return reports;
|
20
|
+
}
|
21
|
+
|
22
|
+
TaskReport get(int index) {
|
23
|
+
return reports.get(index);
|
24
|
+
}
|
25
|
+
}
|
@@ -0,0 +1,93 @@
|
|
1
|
+
package org.embulk.output.multi;
|
2
|
+
|
3
|
+
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
4
|
+
import org.embulk.config.TaskReport;
|
5
|
+
import org.embulk.spi.Page;
|
6
|
+
import org.embulk.spi.TransactionalPageOutput;
|
7
|
+
|
8
|
+
import java.util.concurrent.BlockingQueue;
|
9
|
+
import java.util.concurrent.Callable;
|
10
|
+
import java.util.concurrent.ExecutionException;
|
11
|
+
import java.util.concurrent.ExecutorService;
|
12
|
+
import java.util.concurrent.Executors;
|
13
|
+
import java.util.concurrent.Future;
|
14
|
+
import java.util.concurrent.LinkedBlockingQueue;
|
15
|
+
import java.util.function.Supplier;
|
16
|
+
|
17
|
+
class TransactionalPageOutputDelegate {
|
18
|
+
private static final String THREAD_NAME_FORMAT = "multi-page-output-%s-%d";
|
19
|
+
private final OutputPluginDelegate source;
|
20
|
+
private final TransactionalPageOutput delegate;
|
21
|
+
private final BlockingQueue<Supplier<Object>> taskQueue;
|
22
|
+
private final ExecutorService executorService;
|
23
|
+
private final Future<TaskReport> result;
|
24
|
+
|
25
|
+
TransactionalPageOutputDelegate(
|
26
|
+
int taskIndex,
|
27
|
+
OutputPluginDelegate source,
|
28
|
+
TransactionalPageOutput delegate
|
29
|
+
) {
|
30
|
+
this.source = source;
|
31
|
+
this.delegate = delegate;
|
32
|
+
this.taskQueue = new LinkedBlockingQueue<>();
|
33
|
+
this.executorService = Executors.newSingleThreadExecutor(
|
34
|
+
new ThreadFactoryBuilder().setNameFormat(String.format(THREAD_NAME_FORMAT, source.getPluginCode(), taskIndex)).build()
|
35
|
+
);
|
36
|
+
this.result = executorService.submit(new Worker());
|
37
|
+
}
|
38
|
+
|
39
|
+
void add(Page page) {
|
40
|
+
taskQueue.add(() -> {
|
41
|
+
delegate.add(page);
|
42
|
+
return null;
|
43
|
+
});
|
44
|
+
}
|
45
|
+
|
46
|
+
void finish() {
|
47
|
+
taskQueue.add(() -> {
|
48
|
+
delegate.finish();
|
49
|
+
return null;
|
50
|
+
});
|
51
|
+
}
|
52
|
+
|
53
|
+
void close() {
|
54
|
+
taskQueue.add(() -> {
|
55
|
+
delegate.close();
|
56
|
+
return null;
|
57
|
+
});
|
58
|
+
}
|
59
|
+
|
60
|
+
void abort() {
|
61
|
+
taskQueue.add(() -> {
|
62
|
+
delegate.abort();
|
63
|
+
return null;
|
64
|
+
});
|
65
|
+
}
|
66
|
+
|
67
|
+
TaskReport commit() {
|
68
|
+
taskQueue.add(delegate::commit);
|
69
|
+
try {
|
70
|
+
return result.get();
|
71
|
+
} catch (InterruptedException e) {
|
72
|
+
Thread.currentThread().interrupt();
|
73
|
+
throw new RuntimeException(e);
|
74
|
+
} catch (ExecutionException e) {
|
75
|
+
throw new PluginExecutionException(source, e.getCause());
|
76
|
+
} finally {
|
77
|
+
executorService.shutdown();
|
78
|
+
}
|
79
|
+
}
|
80
|
+
|
81
|
+
private class Worker implements Callable<TaskReport> {
|
82
|
+
@Override
|
83
|
+
public TaskReport call() throws InterruptedException {
|
84
|
+
while (true) {
|
85
|
+
final Supplier<Object> task = taskQueue.take();
|
86
|
+
final Object result = task.get();
|
87
|
+
if (result instanceof TaskReport) {
|
88
|
+
return (TaskReport) result;
|
89
|
+
}
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}
|
93
|
+
}
|
@@ -25,7 +25,6 @@ import static org.embulk.test.Utils.record;
|
|
25
25
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
26
26
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
27
27
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
28
|
-
import static org.junit.jupiter.api.Assertions.fail;
|
29
28
|
|
30
29
|
@EmbulkTest(MultiOutputPlugin.class)
|
31
30
|
class TestMultiOutputPlugin extends EmbulkPluginTest {
|
@@ -116,14 +115,16 @@ class TestMultiOutputPlugin extends EmbulkPluginTest {
|
|
116
115
|
}
|
117
116
|
|
118
117
|
@Test
|
119
|
-
void
|
118
|
+
void testAnOutputFailedFailedAfterOpen() throws IOException {
|
120
119
|
final ConfigSource inConfig = configFromResource("yaml/in_base.yml");
|
121
|
-
final ConfigSource outConfig = configFromResource("yaml/
|
120
|
+
final ConfigSource outConfig = configFromResource("yaml/out_failed_output_after_open.yml");
|
122
121
|
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
123
122
|
System.setOut(new PrintStream(outputStream));
|
124
123
|
|
125
124
|
// Run Embulk without error
|
126
|
-
|
125
|
+
try {
|
126
|
+
runOutput(inConfig, outConfig);
|
127
|
+
} catch (PartialExecutionException ignored) {}
|
127
128
|
|
128
129
|
// All records should be loaded for local_object output plugin.
|
129
130
|
final Record[] values = new Record[]{record(1, "user1", 20), record(2, "user2", 21)};
|
@@ -137,20 +138,16 @@ class TestMultiOutputPlugin extends EmbulkPluginTest {
|
|
137
138
|
}
|
138
139
|
|
139
140
|
@Test
|
140
|
-
void
|
141
|
+
void testAnOutputFailedFailedBeforeOpen() throws IOException {
|
141
142
|
final ConfigSource inConfig = configFromResource("yaml/in_base.yml");
|
142
|
-
final ConfigSource outConfig = configFromResource("yaml/
|
143
|
-
.set("stop_on_failed_output", true);
|
143
|
+
final ConfigSource outConfig = configFromResource("yaml/out_failed_output_before_open.yml");
|
144
144
|
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
145
145
|
System.setOut(new PrintStream(outputStream));
|
146
146
|
|
147
|
-
// Run Embulk
|
147
|
+
// Run Embulk without error
|
148
148
|
try {
|
149
149
|
runOutput(inConfig, outConfig);
|
150
|
-
|
151
|
-
} catch (PartialExecutionException e) {
|
152
|
-
assertTrue(e.getMessage().contains("Failed on output for throw_exception output plugin (pluginIndex: 0)"));
|
153
|
-
}
|
150
|
+
} catch (PartialExecutionException ignored) {}
|
154
151
|
|
155
152
|
// No record should be loaded for local_object output plugin.
|
156
153
|
assertRecords();
|
data/src/test/resources/yaml/{out_stop_on_failed_output.yml → out_failed_output_after_open.yml}
RENAMED
File without changes
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: embulk-output-multi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shinichi ISHIMURA
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-03-
|
11
|
+
date: 2019-03-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
@@ -50,7 +50,7 @@ files:
|
|
50
50
|
- LICENSE
|
51
51
|
- README.md
|
52
52
|
- build.gradle
|
53
|
-
- classpath/embulk-output-multi-0.
|
53
|
+
- classpath/embulk-output-multi-0.2.0.jar
|
54
54
|
- gradle.properties
|
55
55
|
- gradle/dependency-locks/compileClasspath.lockfile
|
56
56
|
- gradle/dependency-locks/testCompileClasspath.lockfile
|
@@ -60,11 +60,18 @@ files:
|
|
60
60
|
- gradlew.bat
|
61
61
|
- lib/embulk/output/multi.rb
|
62
62
|
- settings.gradle
|
63
|
+
- src/main/java/org/embulk/output/multi/ControlDelegate.java
|
63
64
|
- src/main/java/org/embulk/output/multi/MultiOutputPlugin.java
|
65
|
+
- src/main/java/org/embulk/output/multi/OutputPluginDelegate.java
|
66
|
+
- src/main/java/org/embulk/output/multi/PluginExecutionException.java
|
67
|
+
- src/main/java/org/embulk/output/multi/RunControlTask.java
|
68
|
+
- src/main/java/org/embulk/output/multi/TaskReports.java
|
69
|
+
- src/main/java/org/embulk/output/multi/TransactionalPageOutputDelegate.java
|
64
70
|
- src/test/java/org/embulk/output/multi/TestMultiOutputPlugin.java
|
65
71
|
- src/test/resources/yaml/in_base.yml
|
66
72
|
- src/test/resources/yaml/out_base.yml
|
67
|
-
- src/test/resources/yaml/
|
73
|
+
- src/test/resources/yaml/out_failed_output_after_open.yml
|
74
|
+
- src/test/resources/yaml/out_failed_output_before_open.yml
|
68
75
|
homepage: https://github.com/kamatama41/embulk-output-multi
|
69
76
|
licenses:
|
70
77
|
- MIT
|