docstr: Create a thread to run the heartbeat server.
  
  Args:
      heartbeat_exit_callback_func(function): A callback function, which will be called after the 
                                              heartbeat exit.
      exit_func_args(tuple): the argument tuple for calling the heartbeat_exit_callback_func. Defaults to ().
      exit_func_kwargs(dict): the argument dict for calling the heartbeat_exit_callback_func. Defaults to {}.
docstr_tok: ["Create","a","thread","to","run","the","heartbeat","server",".","Args",":","heartbeat_exit_callback_func","(","function",")",":","A","callback","function",",","which","will","be","called","after","the","heartbeat","exit",".","exit_func_args","(","tuple",")",":","the","argument","tuple","for","calling","the","heartbeat_exit_callback_func",".","Defaults","to","(",")",".","exit_func_kwargs","(","dict",")",":","the","argument","dict","for","calling","the","heartbeat_exit_callback_func",".","Defaults","to","{","}","."]
code: def __init__(
      self,
      heartbeat_exit_callback_func,
      exit_func_args=(),
      exit_func_kwargs={},
  ):
      """Create a thread to run the heartbeat server.
  
      Args:
          heartbeat_exit_callback_func(function): A callback function, which will be called after the
                                                  heartbeat exit.
          exit_func_args(tuple): the argument tuple for calling the heartbeat_exit_callback_func. Defaults to ().
          exit_func_kwargs(dict): the argument dict for calling the heartbeat_exit_callback_func. Defaults to {}.
      """
      assert callable(
          heartbeat_exit_callback_func
      ), "It should be a function."
      assert isinstance(exit_func_args, tuple)
      assert isinstance(exit_func_kwargs, dict)
  
      threading.Thread.__init__(self)
      self.grpc_server = grpc.server(
          futures.ThreadPoolExecutor(max_workers=1),
          options=[
              ("grpc.max_receive_message_length", -1),
              ("grpc.max_send_message_length", -1),
          ],
      )
      self.heartbeat_server = GrpcHeartbeatServer()
  
      heartbeat_pb2_grpc.add_GrpcHeartbeatServicer_to_server(
          self.heartbeat_server, self.grpc_server
      )
  
      port = self.grpc_server.add_insecure_port("[::]:0")
  
      self.address = "{}:{}".format(get_ip_address(), port)
  
      self.heartbeat_exit_callback_func = heartbeat_exit_callback_func
      self._exit_func_args = exit_func_args
      self._exit_func_kwargs = exit_func_kwargs
  
imports: import grpc
  import os
  import time
  import threading
  from concurrent import futures
  from parl.remote import remote_constants
  from parl.remote.grpc_heartbeat import heartbeat_pb2
  from parl.remote.grpc_heartbeat import heartbeat_pb2_grpc
  from parl.utils import logger, get_ip_address
  import psutil
  import multiprocessing
label_desc: Use image node_id 1 to create a new HeartbeatServerThread object from inherited base classes: threading with example: obj = HeartbeatServerThread(heartbeat_exit_callback_func, exit_func_args, exit_func_kwargs)