embulk-output-multi 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|