# Buggy code
def launch_task(self, task_id, executable, *args, **kwargs):
    """Handle the actual submission of the task to the executor layer.

    If the app task has the sites attributes not set (default=='all')
    the task is launched on a randomly selected executor from the
    list of executors. This behavior could later be updates to support
    binding to sites based on user specified criteria.

    If the app task specifies a particular set of sites, it will be
    targetted at those specific sites.

    Args:
        task_id (uuid string) : A uuid string that uniquely identifies the task
        executable (callable) : A callable object
        args (list of positional args)
        kwargs (arbitrary keyword arguments)


    Returns:
        Future that tracks the execution of the submitted executable
    """
    hit, memo_fu = self.memoizer.check_memo(task_id, self.tasks[task_id])
    if hit:
        self.handle_update(task_id, memo_fu, memo_cbk=True)
        return memo_fu

    target_sites = self.tasks[task_id]["sites"]
    executor = None
    if isinstance(target_sites, str) and target_sites.lower() == "all":
        # Pick a random site from the list
        site, executor = random.choice(list(self.executors.items()))

    elif isinstance(target_sites, list):
        # Pick a random site from user specified list
        try:
            site = random.choice(target_sites)
            executor = self.executors[site]

        except Exception as e:
            logger.error(
                "Task {}: requests invalid site [{}]".format(task_id, target_sites)
            )
    else:
        logger.error(
            "App {} specifies invalid site option, expects str|list".format(
                self.tasks[task_id]["func"].__name__
            )
        )

    exec_fu = executor.submit(executable, *args, **kwargs)
    exec_fu.retries_left = self.fail_retries - self.tasks[task_id]["fail_count"]
    exec_fu.add_done_callback(partial(self.handle_update, task_id))
    logger.info("Task {} launched on site {}".format(task_id, site))
    return exec_fu


# Traceback (AttributeError)
Traceback (most recent call last):
File "test_regression_233.py", line 38, in <module>
test_checkpoint_availability()
File "test_regression_233.py", line 27, in test_checkpoint_availability
original = run_checkpointed([])
File "test_regression_233.py", line 18, in run_checkpointed
x = cached_rand(i)
File "/home/yadu/src/parsl/parsl/app/app_factory.py", line 76, in __call__
return app_obj(*args, **kwargs)
File "/home/yadu/src/parsl/parsl/app/python_app.py", line 46, in __call__
**kwargs)
File "/home/yadu/src/parsl/parsl/dataflow/dflow.py", line 499, in submit
task_id, func, *new_args, **kwargs)
File "/home/yadu/src/parsl/parsl/dataflow/dflow.py", line 322, in launch_task
exec_fu.add_done_callback(partial(self.handle_update, task_id))
File "/usr/lib/python3.5/concurrent/futures/_base.py", line 376, in add_done_callback
fn(self)
File "/home/yadu/src/parsl/parsl/dataflow/dflow.py", line 218, in handle_update
self.checkpoint(tasks=[task_id])
File "/home/yadu/src/parsl/parsl/dataflow/dflow.py", line 617, in checkpoint
if self.tasks[task_id]['app_fu'].done() and \
AttributeError: 'NoneType' object has no attribute 'done'

# Fixed code
def launch_task(self, task_id, executable, *args, **kwargs):
    """Handle the actual submission of the task to the executor layer.

    If the app task has the sites attributes not set (default=='all')
    the task is launched on a randomly selected executor from the
    list of executors. This behavior could later be updates to support
    binding to sites based on user specified criteria.

    If the app task specifies a particular set of sites, it will be
    targetted at those specific sites.

    Args:
        task_id (uuid string) : A uuid string that uniquely identifies the task
        executable (callable) : A callable object
        args (list of positional args)
        kwargs (arbitrary keyword arguments)


    Returns:
        Future that tracks the execution of the submitted executable
    """
    hit, memo_fu = self.memoizer.check_memo(task_id, self.tasks[task_id])
    if hit:
        self.handle_update(task_id, memo_fu, memo_cbk=True)
        return memo_fu

    target_sites = self.tasks[task_id]["sites"]
    executor = None
    if isinstance(target_sites, str) and target_sites.lower() == "all":
        # Pick a random site from the list
        site, executor = random.choice(list(self.executors.items()))

    elif isinstance(target_sites, list):
        # Pick a random site from user specified list
        try:
            site = random.choice(target_sites)
            executor = self.executors[site]

        except Exception as e:
            logger.error(
                "Task {}: requests invalid site [{}]".format(task_id, target_sites)
            )
    else:
        logger.error(
            "App {} specifies invalid site option, expects str|list".format(
                self.tasks[task_id]["func"].__name__
            )
        )

    exec_fu = executor.submit(executable, *args, **kwargs)
    self.tasks[task_id]["status"] = States.running
    exec_fu.retries_left = self.fail_retries - self.tasks[task_id]["fail_count"]
    exec_fu.add_done_callback(partial(self.handle_update, task_id))
    logger.info("Task {} launched on site {}".format(task_id, site))
    return exec_fu