Flink will automatically stop the execution of a DataStream program once all sources have finished to provide data, i.e., when all SourceFunction return from the run() method.
The DeserializationSchema.isEndOfStream() method can be used to tell a built-in SourceFunction such as a KafkaConsumer that it should leave the run() method.
If you implement your own SourceFunction you can leave run() after you ingested all data.
Note, that Flink won't wait for all processing time timers but will immediately shutdown the program after the last in-flight record was processed.
Event-time timers will be handled because each source emits a Long.MAX_VALUE watermark after it emitted its last record.