<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def ready_nodes_iter(self, graph_copy):
 """Generate ready targets from the graph `graph_copy`. The input graph is mutated by this method, so it has to be a mutable copy of the graph (e.g. not original copy, or read-only view). Caller **must** call `done()` after processing every generated target, so additional ready targets can be added to the queue. The invariant: a target may be yielded from this generator only after all its descendant targets were notified "done". """


        def is_ready(target_name):
            """Return True if the node `target_name` is "ready" in the graph
               `graph_copy`.

            "Ready" means that the graph doesn't contain any more nodes that
            `target_name` depends on (e.g. it has no successors).
            """
            try:
                next(graph_copy.successors(target_name))
            except StopIteration:
                return True
            return False

        ready_nodes = deque(sorted(
            target_name for target_name in graph_copy.nodes
            if is_ready(target_name)))
        produced_event = threading.Event()
        failed_event = threading.Event()

        def make_done_callback(target: Target):
            """Return a callable "done" notifier to
               report a target as processed."""

            def done_notifier():
                """Mark target as done, adding new ready nodes to queue"""
                if graph_copy.has_node(target.name):
                    affected_nodes = list(sorted(
                        graph_copy.predecessors(target.name)))
                    graph_copy.remove_node(target.name)
                    ready_nodes.extend(
                        target_name for target_name in affected_nodes
                        if is_ready(target_name))
                    produced_event.set()

            return done_notifier

        def make_retry_callback(target: Target):
            """Return a callable "retry" notifier to
               report a target as in need of retry.
               Currently for tests we rebuild the target
               when it's not necessary."""

            def retry_notifier():
                """Mark target as retry, re-entering node to end of queue"""
                if graph_copy.has_node(target.name):
                    ready_nodes.append(target.name)
                    produced_event.set()

            return retry_notifier

        def make_fail_callback(target: Target):
            """Return a callable "fail" notifier to
               report a target as failed after all retries."""

            def fail_notifier(ex):
                """Mark target as failed, taking it and ancestors
                   out of the queue"""
                # TODO(Dana) separate "failed to build target" errors from
                # "failed to run" errors.
                # see: https://github.com/resonai/ybt/issues/124
                if isinstance(ex, CalledProcessError):
                    sys.stdout.write(ex.stdout.decode('utf-8'))
                    sys.stderr.write(ex.stderr.decode('utf-8'))
                if graph_copy.has_node(target.name):
                    self.failed_nodes[target.name] = ex
                    # removing all ancestors (nodes that depend on this one)
                    affected_nodes = get_ancestors(graph_copy, target.name)
                    graph_copy.remove_node(target.name)
                    for affected_node in affected_nodes:
                        if affected_node in self.skipped_nodes:
                            continue
                        if graph_copy.has_node(affected_node):
                            self.skipped_nodes.append(affected_node)
                            graph_copy.remove_node(affected_node)
                    if self.conf.continue_after_fail:
                        logger.info('Failed target: {} due to error: {}',
                                    target.name, ex)
                        produced_event.set()
                    else:
                        failed_event.set()
                        fatal('`{}\': {}', target.name, ex)

            return fail_notifier

        while True:
            while len(ready_nodes) == 0:
                if graph_copy.order() == 0:
                    return
                if failed_event.is_set():
                    return
                produced_event.wait(0.5)
            produced_event.clear()
            next_node = ready_nodes.popleft()
            node = self.targets[next_node]
            node.done = make_done_callback(node)
            # TODO(bergden) retry assumes no need to update predecessors:
            # This means we don't support retries for targets that are
            # prerequisites of other targets (builds, installs)
            node.retry = make_retry_callback(node)
            node.fail = make_fail_callback(node)
            yield node