Currently if you provide an ANN search using the `knn` search section, the search doesn't respect cancellation. This is because ANN searches use their own Lucene data structures that aren't covered by `ExitableDirectoryReader`.

This class is forked from Lucene, and in Lucene's version we recently added fine-grained cancellation for ANN search (https://github.com/apache/lucene/pull/833). So I think we just need to fold in the changes to the Elasticsearch version.
server/src/main/java/org/elasticsearch/search/internal/ExitableDirectoryReader.java
public void visitDocIDs(PointValues.IntersectVisitor visitor) throws IOException {
            queryCancellation.checkCancelled();
            in.visitDocIDs(visitor);
        }
        @Override
        public void visitDocValues(PointValues.IntersectVisitor visitor) throws IOException {
            queryCancellation.checkCancelled();
            exitableIntersectVisitor.setIntersectVisitor(visitor);
            in.visitDocValues(exitableIntersectVisitor);
        }
    }
    private static class ExitableIntersectVisitor implements PointValues.IntersectVisitor {
        static final int MAX_CALLS_BEFORE_QUERY_TIMEOUT_CHECK = (1 << 13) - 1; 
        private final QueryCancellation queryCancellation;
        private PointValues.IntersectVisitor in;
        private int calls;
        private ExitableIntersectVisitor(QueryCancellation queryCancellation) {
            this.queryCancellation = queryCancellation;
        }
        private void setIntersectVisitor(PointValues.IntersectVisitor in) {
            this.in = in;
        }
        private void checkAndThrowWithSampling() {
            if ((calls++ & MAX_CALLS_BEFORE_QUERY_TIMEOUT_CHECK) == 0) {
                queryCancellation.checkCancelled();
            }
        }
        @Override
        public void visit(int docID) throws IOException {
            checkAndThrowWithSampling();