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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bd9a500d9ed46b10465db1c1521c60324a90526b
4
- data.tar.gz: fc26433a4aabf02ad3e474c503284801e816c0d8
3
+ metadata.gz: cb52fdd126c8c9e752f186479dfdd1e8bb60560c
4
+ data.tar.gz: fd2f7fa9b9ee60bd2d7aaaa598b9ade60c611abf
5
5
  SHA512:
6
- metadata.gz: f65a6b68738a9af5638550666c3340d30f8e75fcbe7b142843a977492c98179c4d7f5c1dd875041771608e7e72c4f57113432ca55695b5f07bd378a191446cdd
7
- data.tar.gz: 3a2cdbec13b5e9a507fc348ed69ef3270e5fbd90275e03ba1797713e12d609c489eb1dd626592d4bd13aa66879c6bc2cd771343dc292a4862557e65e7f91eb22
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.2"
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.1
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.2
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
- private static final String CONFIG_NAME_OUTPUT_TASK_REPORTS = "output_task_reports";
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, executorService);
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, executorService);
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), task.getStopOnFailedOutput())
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 (TransactionalPageOutput output : delegates) {
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 (TransactionalPageOutput output : delegates) {
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 (TransactionalPageOutput output : delegates) {
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 (TransactionalPageOutput output : delegates) {
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
- for (TransactionalPageOutput output : delegates) {
149
- reports.add(output.commit());
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 | ExecutionException e) {
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, executorService)));
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 testStopOnFailedOutputIsFalse() throws IOException {
118
+ void testAnOutputFailedFailedAfterOpen() throws IOException {
120
119
  final ConfigSource inConfig = configFromResource("yaml/in_base.yml");
121
- final ConfigSource outConfig = configFromResource("yaml/out_stop_on_failed_output.yml");
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
- runOutput(inConfig, outConfig);
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 testStopOnFailedOutputIsTrue() throws IOException {
141
+ void testAnOutputFailedFailedBeforeOpen() throws IOException {
141
142
  final ConfigSource inConfig = configFromResource("yaml/in_base.yml");
142
- final ConfigSource outConfig = configFromResource("yaml/out_stop_on_failed_output.yml")
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
- fail("No exception");
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();
@@ -0,0 +1,13 @@
1
+ type: multi
2
+ outputs:
3
+ - type: throw_exception
4
+ thrown_on: COMMIT
5
+ source:
6
+ type: stdout
7
+ - type: local_object
8
+ incremental: true
9
+ incremental_column: id
10
+ - type: throw_exception
11
+ thrown_on: BEFORE_TRANSACTION
12
+ source:
13
+ type: stdout
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.1.1
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-03 00:00:00.000000000 Z
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.1.1.jar
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/out_stop_on_failed_output.yml
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