প্রতিটি অ্যাসিঙ্ক কলের একটি ডেডলাইন দরকার

এই পর্বে তৈরি প্রতিটি পাইপলাইনের একটি অলিখিত অনুমান আছে: lookupUser, fetchQuote এবং অন্য প্রতিটি অ্যাসিঙ্ক কল শেষ পর্যন্ত সম্পন্ন হবে। বাস্তব সিস্টেমে ডাউনস্ট্রিম নির্ভরতা আটকে যেতে পারে, যেমন:

  • আটকে থাকা সংযোগ
  • অতিরিক্ত চাপে থাকা সার্ভিস
  • নেটওয়ার্ক পার্টিশন

ডেডলাইন ছাড়া সেই কলের জন্য অপেক্ষা করা CompletableFuture কখনোই সম্পন্ন হবে না। যে get() বা join() তার ওপর অপেক্ষা করছে, সেটি চিরতরে ব্লক থাকবে; অথবা কলার কল সাইটে টাইমআউট যুক্ত করবে, কিন্তু ততক্ষণে সাধারণত অনির্দিষ্টকাল ব্লক হওয়া ছাড়া ভালো কোনো বিকল্প থাকে না।

get(timeout) দিয়ে সীমিত অপেক্ষা

সাধারণ Future-এর সাথে আগে পরিচিত টাইমড get() সবচেয়ে মৌলিক হাতিয়ার:

import java.util.concurrent.*;

public class GetTimeoutExample {
    static record Quote(String supplierId, double price) {}

    public static void main(String[] args) {
        CompletableFuture<Quote> quoteFuture = new CompletableFuture<>();

        try {
            Quote quote = quoteFuture.get(500, TimeUnit.MILLISECONDS);
            System.out.println(quote);
        } catch (TimeoutException e) {
            System.out.println("Timed out after 500ms");
        } catch (InterruptedException | ExecutionException e) {
            Thread.currentThread().interrupt();
            System.out.println("Wait interrupted or failed");
        }
    }
}

এই কোডটি কেবল অপেক্ষার সময়সীমা বেঁধে দেয়; ফিউচার নিজে বাতিল হয় না। TimeoutException পাওয়ার পরও quoteFuture অসম্পূর্ণ থেকে যায়, এবং বাস্তব পাইপলাইনে পেছনের কাজ চলতে থাকলে তাও বাতিল হয় না।

পাইপলাইনে ডেডলাইন বসানো: orTimeout

orTimeout(duration, unit) ডেডলাইন সরাসরি CompletableFuture-এর সঙ্গে যুক্ত করে, কোনো নির্দিষ্ট get() কলারের সঙ্গে নয়। নির্ধারিত সময়ের মধ্যে ফিউচার সম্পন্ন না হলে এটি TimeoutException সহ ব্যতিক্রমসহ সম্পন্ন হয়; পরবর্তী প্রতিটি স্টেজ এই ব্যর্থতাকে অন্য যেকোনো ব্যতিক্রমের মতোই দেখে।

orTimeout ফিউচারকে নিজে ব্যর্থ করে বলে এটি আগের ত্রুটি-পরিচালনা প্রবন্ধের টুলগুলোর সাথে স্বাভাবিকভাবে মেশে:

  • exceptionally
  • handle
  • allOf

এই সবগুলো টাইমআউটকে অন্য যেকোনো ব্যর্থতার মতোই দেখে।

import java.util.concurrent.*;

public class OrTimeoutExample {
    static record Quote(String supplierId, double price, boolean available) {
        static Quote unavailable(String supplierId) {
            return new Quote(supplierId, 0, false);
        }
    }

    public static void main(String[] args) throws Exception {
        ExecutorService pool = Executors.newFixedThreadPool(2);

        try {
            CompletableFuture<Quote> quoteFuture = CompletableFuture
                .supplyAsync(() -> fetchQuote("supplier-1"), pool)
                .orTimeout(500, TimeUnit.MILLISECONDS)
                .exceptionally(ex -> Quote.unavailable("supplier-1"));

            System.out.println(quoteFuture.get());
        } finally {
            pool.shutdown();
        }
    }

    static Quote fetchQuote(String supplierId) {
        return new Quote(supplierId, 120.50, true);
    }
}

এখানে orTimeout পাইপলাইনের ভেতরেই ডেডলাইন বসায়। ফিউচারটি সময়মতো শেষ না হলে exceptionally স্টেজটি ফলব্যাক Quote তৈরি করে, ফলে বাইরের কলার একটি ব্যর্থতা নয়, বরং একটি মান পায়।

ব্যর্থতার বদলে ফলব্যাক মান: completeOnTimeout

কিছু সময় টাইমআউটকে ব্যতিক্রম না ভেবে “ডিফল্ট মান ব্যবহার করো” বোঝানো দরকার। completeOnTimeout(fallbackValue, duration, unit) ঠিক সেটাই করে: ডেডলাইন পেরিয়ে গেলে ফিউচার ব্যর্থ না হয়ে ফলব্যাক মানসহ সফলভাবে সম্পন্ন হয়।

import java.util.concurrent.*;

public class CompleteOnTimeoutExample {
    static record Quote(String supplierId, double price, boolean available) {
        static Quote unavailable(String supplierId) {
            return new Quote(supplierId, 0, false);
        }
    }

    public static void main(String[] args) throws Exception {
        ExecutorService pool = Executors.newFixedThreadPool(2);

        try {
            CompletableFuture<Quote> quoteFuture = CompletableFuture
                .supplyAsync(() -> fetchQuote("supplier-1"), pool)
                .completeOnTimeout(Quote.unavailable("supplier-1"), 500, TimeUnit.MILLISECONDS);

            System.out.println(quoteFuture.get());
        } finally {
            pool.shutdown();
        }
    }

    static Quote fetchQuote(String supplierId) {
        return new Quote(supplierId, 120.50, true);
    }
}

এটি orTimeoutexceptionally-এর জুটি থেকে বেশি সরাসরি, যখন ফলব্যাক একটি সাধারণ মান। ব্যতিক্রম পরীক্ষা করার দরকার হয় না; কোড পড়লেই বোঝা যায় সময় শেষ হলে কোন মান ফেরত আসবে।

cancel() আসলে কী করে

Future এবং CompletableFuture উভয়েই cancel(mayInterruptIfRunning) প্রকাশ করে। এটি আসলে কী করে আর কী করে না, তা স্পষ্ট করা জরুরি। cancel() কল করলে ফিউচার বাতিল অবস্থায় চিহ্নিত হয় এবং অপেক্ষমাণ get() একটি CancellationException ছুড়ে দেয়। FutureTask-এর মতো সাধারণ Future বাস্তবায়নে mayInterruptIfRunning সত্য হলে চলমান থ্রেডকে ইন্টারাপ্ট করার চেষ্টা করা হয়। তবে CompletableFuture-এর নিজস্ব বাস্তবায়নে এই ফ্ল্যাগের কোনো প্রভাব নেই; এটি কোনো চলমান টাস্ককে ইন্টারাপ্ট করে না। এটি কেবল ফিউচারের সম্পন্ন হওয়া এবং নির্ভরশীল স্টেজগুলোর চালানো বাতিল করে।

  • যা করে: cancel() ফিউচার বাতিল করে, অপেক্ষমাণ get()-এ CancellationException ছুড়ে দেয় এবং নির্ভরশীল স্টেজ চালানো বন্ধ রাখে।
  • যা করে না: এটি supplier-এর ভেতরে চলা কোড জোর করে থামায় না; চলমান কোড নিজে ইন্টারাপ্ট চেক না করলে পুরো কাজ থামানো যায় না।
Share