# Buggy code
def remote_decorator(func):
    func_name = "{}.{}".format(func.__module__, func.__name__)
    if func_id is None:
        # Compute the function ID as a hash of the function name as well as the
        # source code. We could in principle hash in the values in the closure
        # of the function, but that is likely to introduce non-determinism in
        # the computation of the function ID.
        function_id_hash = hashlib.sha1()
        function_id_hash.update(func_name.encode("ascii"))
        function_id_hash.update(inspect.getsource(func).encode("ascii"))
        function_id = function_id_hash.digest()
        assert len(function_id) == 20
        function_id = FunctionID(function_id)
    else:
        function_id = func_id

    def func_call(*args, **kwargs):
        """This gets run immediately when a worker calls a remote function."""
        check_connected()
        check_main_thread()
        args = list(args)
        # Fill in the remaining arguments.
        args.extend(
            [
                kwargs[keyword] if keyword in kwargs else default
                for keyword, default in keyword_defaults[len(args) :]
            ]
        )
        if any([arg is funcsigs._empty for arg in args]):
            raise Exception(
                "Not enough arguments were provided to {}.".format(func_name)
            )
        if _mode() == PYTHON_MODE:
            # In PYTHON_MODE, remote calls simply execute the function. We copy
            # the arguments to prevent the function call from mutating them and
            # to match the usual behavior of immutable remote objects.
            try:
                _env()._running_remote_function_locally = True
                result = func(*copy.deepcopy(args))
            finally:
                _env()._reinitialize()
                _env()._running_remote_function_locally = False
            return result
        objectids = _submit_task(function_id, func_name, args)
        if len(objectids) == 1:
            return objectids[0]
        elif len(objectids) > 1:
            return objectids

    def func_executor(arguments):
        """This gets run when the remote function is executed."""
        result = func(*arguments)
        return result

    def func_invoker(*args, **kwargs):
        """This is used to invoke the function."""
        raise Exception(
            "Remote functions cannot be called directly. Instead "
            "of running '{}()', try '{}.remote()'.".format(func_name, func_name)
        )

    func_invoker.remote = func_call
    func_invoker.executor = func_executor
    func_invoker.is_remote = True
    func_name = "{}.{}".format(func.__module__, func.__name__)
    func_invoker.func_name = func_name
    if sys.version_info >= (3, 0):
        func_invoker.__doc__ = func.__doc__
    else:
        func_invoker.func_doc = func.func_doc

    sig_params = [(k, v) for k, v in funcsigs.signature(func).parameters.items()]
    keyword_defaults = [(k, v.default) for k, v in sig_params]
    has_vararg_param = any([v.kind == v.VAR_POSITIONAL for k, v in sig_params])
    func_invoker.has_vararg_param = has_vararg_param
    has_kwargs_param = any([v.kind == v.VAR_KEYWORD for k, v in sig_params])
    check_signature_supported(
        has_kwargs_param, has_vararg_param, keyword_defaults, func_name
    )

    # Everything ready - export the function
    if worker.mode in [SCRIPT_MODE, SILENT_MODE]:
        export_remote_function(
            function_id, func_name, func, num_return_vals, num_cpus, num_gpus
        )
    elif worker.mode is None:
        worker.cached_remote_functions.append(
            (function_id, func_name, func, num_return_vals, num_cpus, num_gpus)
        )
    return func_invoker


# Traceback (OSError)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/rkn/Workspace/ray/python/ray/worker.py", line 1886, in remote
return make_remote_decorator(num_return_vals, num_cpus, num_gpus)(args[0])
File "/Users/rkn/Workspace/ray/python/ray/worker.py", line 1798, in remote_decorator
function_id_hash.update(inspect.getsource(func).encode("ascii"))
File "/Users/rkn/anaconda3/lib/python3.6/inspect.py", line 938, in getsource
lines, lnum = getsourcelines(object)
File "/Users/rkn/anaconda3/lib/python3.6/inspect.py", line 925, in getsourcelines
lines, lnum = findsource(object)
File "/Users/rkn/anaconda3/lib/python3.6/inspect.py", line 756, in findsource
raise OSError('could not get source code')
OSError: could not get source code

# Fixed code
def remote_decorator(func):
    func_name = "{}.{}".format(func.__module__, func.__name__)
    if func_id is None:
        function_id = compute_function_id(func_name, func)
    else:
        function_id = func_id

    def func_call(*args, **kwargs):
        """This gets run immediately when a worker calls a remote function."""
        check_connected()
        check_main_thread()
        args = list(args)
        # Fill in the remaining arguments.
        args.extend(
            [
                kwargs[keyword] if keyword in kwargs else default
                for keyword, default in keyword_defaults[len(args) :]
            ]
        )
        if any([arg is funcsigs._empty for arg in args]):
            raise Exception(
                "Not enough arguments were provided to {}.".format(func_name)
            )
        if _mode() == PYTHON_MODE:
            # In PYTHON_MODE, remote calls simply execute the function. We copy
            # the arguments to prevent the function call from mutating them and
            # to match the usual behavior of immutable remote objects.
            try:
                _env()._running_remote_function_locally = True
                result = func(*copy.deepcopy(args))
            finally:
                _env()._reinitialize()
                _env()._running_remote_function_locally = False
            return result
        objectids = _submit_task(function_id, func_name, args)
        if len(objectids) == 1:
            return objectids[0]
        elif len(objectids) > 1:
            return objectids

    def func_executor(arguments):
        """This gets run when the remote function is executed."""
        result = func(*arguments)
        return result

    def func_invoker(*args, **kwargs):
        """This is used to invoke the function."""
        raise Exception(
            "Remote functions cannot be called directly. Instead "
            "of running '{}()', try '{}.remote()'.".format(func_name, func_name)
        )

    func_invoker.remote = func_call
    func_invoker.executor = func_executor
    func_invoker.is_remote = True
    func_name = "{}.{}".format(func.__module__, func.__name__)
    func_invoker.func_name = func_name
    if sys.version_info >= (3, 0):
        func_invoker.__doc__ = func.__doc__
    else:
        func_invoker.func_doc = func.func_doc

    sig_params = [(k, v) for k, v in funcsigs.signature(func).parameters.items()]
    keyword_defaults = [(k, v.default) for k, v in sig_params]
    has_vararg_param = any([v.kind == v.VAR_POSITIONAL for k, v in sig_params])
    func_invoker.has_vararg_param = has_vararg_param
    has_kwargs_param = any([v.kind == v.VAR_KEYWORD for k, v in sig_params])
    check_signature_supported(
        has_kwargs_param, has_vararg_param, keyword_defaults, func_name
    )

    # Everything ready - export the function
    if worker.mode in [SCRIPT_MODE, SILENT_MODE]:
        export_remote_function(
            function_id, func_name, func, num_return_vals, num_cpus, num_gpus
        )
    elif worker.mode is None:
        worker.cached_remote_functions.append(
            (function_id, func_name, func, num_return_vals, num_cpus, num_gpus)
        )
    return func_invoker