def METHOD_NAME(self, step: Step, result: T) -> T:
    # We don't do anything with uncacheable steps.
    if not step.cache_results:
        return result
    step_info = self.step_info(step)
    if step_info.state != StepState.RUNNING:
        raise StepStateError(step, step_info.state)
    # Update step info and save step execution metadata.
    # This needs to be done *before* adding the result to the cache, since adding
    # the result to the cache will commit the step dataset, making it immutable.
    step_info.end_time = utc_now_datetime()
    step_info.result_location = self._step_location(step)
    self._update_step_info(step_info)
    self.cache[step] = result
    if hasattr(result, "__next__"):
        assert isinstance(result, Iterator)
        # Caching the iterator will consume it, so we write it to the cache and then read from the cache
        # for the return value.
        result = self.cache[step]
    self.locks.pop(step).release()
    return result