2323import org .elasticsearch .ingest .ConfigurationUtils ;
2424import org .elasticsearch .ingest .IngestDocument ;
2525import org .elasticsearch .ingest .Processor ;
26+ import org .elasticsearch .ingest .WrappingProcessor ;
27+ import org .elasticsearch .script .ScriptService ;
2628
2729import java .util .ArrayList ;
2830import java .util .List ;
2931import java .util .Map ;
3032import java .util .Set ;
3133import java .util .concurrent .CopyOnWriteArrayList ;
3234import java .util .function .BiConsumer ;
33-
34- import org .elasticsearch .ingest .WrappingProcessor ;
35- import org .elasticsearch .script .ScriptService ;
35+ import java .util .function .Consumer ;
3636
3737import static org .elasticsearch .ingest .ConfigurationUtils .newConfigurationException ;
3838import static org .elasticsearch .ingest .ConfigurationUtils .readBooleanProperty ;
5050public final class ForEachProcessor extends AbstractProcessor implements WrappingProcessor {
5151
5252 public static final String TYPE = "foreach" ;
53+ static final int MAX_RECURSE_PER_THREAD = 10 ;
5354
5455 private final String field ;
5556 private final Processor processor ;
5657 private final boolean ignoreMissing ;
58+ private final Consumer <Runnable > genericExecutor ;
5759
58- ForEachProcessor (String tag , String field , Processor processor , boolean ignoreMissing ) {
60+ ForEachProcessor (String tag , String field , Processor processor , boolean ignoreMissing , Consumer < Runnable > genericExecutor ) {
5961 super (tag );
6062 this .field = field ;
6163 this .processor = processor ;
6264 this .ignoreMissing = ignoreMissing ;
65+ this .genericExecutor = genericExecutor ;
6366 }
6467
6568 boolean isIgnoreMissing () {
@@ -91,6 +94,7 @@ void innerExecute(int index, List<?> values, List<Object> newValues, IngestDocum
9194
9295 Object value = values .get (index );
9396 Object previousValue = document .getIngestMetadata ().put ("_value" , value );
97+ final Thread thread = Thread .currentThread ();
9498 processor .execute (document , (result , e ) -> {
9599 if (e != null ) {
96100 newValues .add (document .getIngestMetadata ().put ("_value" , previousValue ));
@@ -99,7 +103,15 @@ void innerExecute(int index, List<?> values, List<Object> newValues, IngestDocum
99103 handler .accept (null , null );
100104 } else {
101105 newValues .add (document .getIngestMetadata ().put ("_value" , previousValue ));
102- innerExecute (index + 1 , values , newValues , document , handler );
106+ if (thread == Thread .currentThread () && (index + 1 ) % MAX_RECURSE_PER_THREAD == 0 ) {
107+ // we are on the same thread and we need to fork to another thread to avoid recursive stack overflow on a single thread
108+ // only fork after 10 recursive calls, then fork every 10 to keep the number of threads down
109+ genericExecutor .accept (() -> innerExecute (index + 1 , values , newValues , document , handler ));
110+ } else {
111+ // we are on a different thread (we went asynchronous), it's safe to recurse
112+ // or we have recursed less then 10 times with the same thread, it's safe to recurse
113+ innerExecute (index + 1 , values , newValues , document , handler );
114+ }
103115 }
104116 });
105117 }
@@ -125,9 +137,11 @@ public Processor getInnerProcessor() {
125137 public static final class Factory implements Processor .Factory {
126138
127139 private final ScriptService scriptService ;
140+ private final Consumer <Runnable > genericExecutor ;
128141
129- Factory (ScriptService scriptService ) {
142+ Factory (ScriptService scriptService , Consumer < Runnable > genericExecutor ) {
130143 this .scriptService = scriptService ;
144+ this .genericExecutor = genericExecutor ;
131145 }
132146
133147 @ Override
@@ -143,7 +157,7 @@ public ForEachProcessor create(Map<String, Processor.Factory> factories, String
143157 Map .Entry <String , Map <String , Object >> entry = entries .iterator ().next ();
144158 Processor processor =
145159 ConfigurationUtils .readProcessor (factories , scriptService , entry .getKey (), entry .getValue ());
146- return new ForEachProcessor (tag , field , processor , ignoreMissing );
160+ return new ForEachProcessor (tag , field , processor , ignoreMissing , genericExecutor );
147161 }
148162 }
149163}
0 commit comments