Q:

How to send message to stop polling and exit from application

I have spring integration application and I need to close it after all data were processed. If I explicitly call appContext.close() then not all data could be processed in time(unless I set Thread.sleep()). If I do not call close on application context then application doesn't stop since I have background poller that does not allow application to close automatically. So how to signal to stop whole application in one of my service activator(last in the chain of processing)?

first bean reads data from storage line by line and send it via gateway.send(data) in while loop
processing chain that is happening in parallel
than all threads sends messages into single thread via Pollable Queue
And here I should stop application if I realise that all messages read in the first bean were processed

I tried to stop last service activator using controlbus but it didn't help
Thanks
UPDATE
here are some code examples:
Runner:
public class Runner {
static Logger log = LoggerFactory.getLogger(Service2.class);

public static void main(String[] args) throws InterruptedException {
    log.info("START APP");
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    RootService service = context.getBean(RootService.class);
    service.start();
    service.stop();
    context.close();
    log.info("END APP");
}

}
RootService:
    @Component
public class RootService {

    Logger log = LoggerFactory.getLogger(RootService.class);

    @Autowired
    MyGateway gateway;

    int totalSize = 0;

    public void start() {
        List<String> source = generateSource();
        totalSize = source.size();
        //imitate very long but finite process
        for (String s : source) {
            gateway.send(s, totalSize);
        }
        log.info("end sending data");
    }

    public void stop() throws InterruptedException {
        log.info("sending stop signal...");
        while (gateway.sendStop(totalSize)<0) {
            Thread.sleep(100);
            log.info("sending stop signal...");
        }
        log.info("THE END");
    }

    private List<String> generateSource() {
        List<String> result = new ArrayList<String>();
        for (int i = 0; i < 15; i++) {
            result.add("data" + i);
        }
        return result;
    }
}

Service1
@Component
public class Service1 {

    public String dodo(String data) throws InterruptedException {
        //doing a job in parallel
        Thread.sleep(100);
        return data + "-" + Thread.currentThread().getName();
    }
}

Service2:
    @Component
public class Service2 {
    Logger log = LoggerFactory.getLogger(Service2.class);
    int counter = 0;

    public void dodo(String data) {
        log.info("data: {}-{}", data, Thread.currentThread().getName());
        counter++;
        log.info("counter: {}", counter);
    }

    public Integer dodo(Integer data) {
        if (counter < data) {
            return -1;
        } else {
            return 0;
        }
    }
}

@Component
public class ErrorHandler {

    Logger log = LoggerFactory.getLogger(Service2.class);

    public void handleError(Message<?> message) {
        log.info("ERROR: {}", message);
    }
}

and xml config
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
        http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd"
             xmlns:beans="http://www.springframework.org/schema/beans"
             xmlns:task="http://www.springframework.org/schema/task">

    <gateway id="myGateway"
             service-interface="com.dimas.MyGateway"
             default-request-channel="channel1"
             error-channel="errorChannel"
             default-reply-timeout="3000">
        <method name="send" request-channel="channel1"/>
        <method name="sendStop" request-channel="channel2" reply-channel="channel3"/>
    </gateway>

    <channel id="channel1">
        <dispatcher task-executor="executor"/>
    </channel>
    <channel id="channel2">
        <queue/>
    </channel>
    <channel id="channel3">
        <queue/>
    </channel>
    <channel id="errorChannel"/>

    <service-activator input-channel="errorChannel" ref="errorHandler" method="handleError"/>

    <service-activator input-channel="channel1" output-channel="channel2" ref="service1"/>
    <service-activator input-channel="channel2" output-channel="channel3" ref="service2">
        <poller fixed-delay="0"/>
    </service-activator>

    <task:executor id="executor" pool-size="2"/>
</beans:beans>

Now it works - app is stopped when all data is processed and it returns stop code 0 but i see in log a lot of errors like:

ErrorMessage
  [payload=org.springframework.messaging.core.DestinationResolutionException:
  no output-channel or replyChannel header available,
  headers={id=639ca939-8110-4486-6a2b-5d36c7bfdbcd,
  timestamp=1475872251269}]

Error handler successfully catch it but something is wrong
I realized where was the issue. Last Service2 returns soemthing for any income message. Redid it so it responds code only for stopRequest request.
Just wondering if there is simpler solution

A:

It's not clear why, if you can detect you are complete, you can't close the application context. 
You could replace the default taskScheduler with one that uses daemon threads.
EDIT
First of all, you don't need all those queue channels; simply having channel1 being an executor channel will give you the concurrency - using queue channels after that just adds overhead.
In any case, to determine when the process is complete, just add a getCounter() method to Service2; then, in your main method, get service2 from the context and wait until the counter has incremented to the number you expect.
Or, you can add a countdown latch to service2 - add a method to set it...
CountDownLatch latch = new CountDownLatch(source.size());
service2.setCountDownLatch(latch);
for (

...
if (!latch.await(...)) { // add a timeout in case is never completes)
    // failure
}

