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 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