mirror of
https://github.com/facebook/sapling.git
synced 2024-10-10 16:57:49 +03:00
bfad766a21
Summary: We've seen what appears to be phantom calls to shutdown() so we'd like to add some degree of auditing. This diff adds a new method with some context; this will allow us to distinguish between `eden stop`, `eden restart` and eden server internal calls to the `shutdown` method. It may still be possible that something else is calling our shutdown method, but it seems unlikely as we're only accessible to our own code via a unix domain socket. Reviewed By: chadaustin Differential Revision: D8341595 fbshipit-source-id: 50d58ea0b56e5f42cd37c404048d710bde0d13a3
120 lines
4.2 KiB
Python
120 lines
4.2 KiB
Python
# Copyright (c) 2016-present, Facebook, Inc.
|
|
# All rights reserved.
|
|
#
|
|
# This source code is licensed under the BSD-style license found in the
|
|
# LICENSE file in the root directory of this source tree. An additional grant
|
|
# of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
|
|
|
import os
|
|
from typing import Any, cast
|
|
|
|
from facebook.eden import EdenService
|
|
from thrift.protocol.THeaderProtocol import THeaderProtocol
|
|
from thrift.Thrift import TApplicationException
|
|
from thrift.transport.THeaderTransport import THeaderTransport
|
|
from thrift.transport.TSocket import TSocket
|
|
from thrift.transport.TTransport import TTransportException
|
|
|
|
|
|
SOCKET_PATH = "socket"
|
|
|
|
|
|
class EdenNotRunningError(Exception):
|
|
def __init__(self, eden_dir):
|
|
msg = "edenfs daemon does not appear to be running: tried %s" % eden_dir
|
|
super(EdenNotRunningError, self).__init__(msg)
|
|
self.eden_dir = eden_dir
|
|
|
|
|
|
# Monkey-patch EdenService.EdenError's __str__() behavior to just return the
|
|
# error message. By default it returns the same data as __repr__(), which is
|
|
# ugly to show to users.
|
|
def _eden_thrift_error_str(ex):
|
|
return ex.message
|
|
|
|
|
|
# TODO: https://github.com/python/mypy/issues/2427
|
|
cast(Any, EdenService.EdenError).__str__ = _eden_thrift_error_str
|
|
|
|
|
|
class EdenClient(EdenService.Client):
|
|
"""
|
|
EdenClient is a subclass of EdenService.Client that provides
|
|
a few additional conveniences:
|
|
|
|
- Smarter constructor
|
|
- Implement the context manager __enter__ and __exit__ methods, so it can
|
|
be used in with statements.
|
|
"""
|
|
|
|
def __init__(self, eden_dir=None, socket_path=None):
|
|
if socket_path is not None:
|
|
self._socket_path = socket_path
|
|
elif eden_dir is not None:
|
|
self._socket_path = os.path.join(eden_dir, SOCKET_PATH)
|
|
else:
|
|
raise TypeError("one of eden_dir or socket_path is required")
|
|
self._socket = TSocket(unix_socket=self._socket_path)
|
|
# We used to set a timeout here, but picking the right duration is hard,
|
|
# and safely retrying an arbitrary thrift call may not be safe. So we
|
|
# just leave the client with no timeout.
|
|
# self._socket.setTimeout(60000) # in milliseconds
|
|
self._transport = THeaderTransport(self._socket)
|
|
self._protocol = THeaderProtocol(self._transport)
|
|
super(EdenClient, self).__init__(self._protocol)
|
|
|
|
def __enter__(self):
|
|
self.open()
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_value, exc_traceback):
|
|
self.close()
|
|
|
|
def open(self):
|
|
try:
|
|
self._transport.open()
|
|
except TTransportException as ex:
|
|
self.close()
|
|
if ex.type == TTransportException.NOT_OPEN:
|
|
raise EdenNotRunningError(self._socket_path)
|
|
raise
|
|
|
|
def close(self):
|
|
if self._transport is not None:
|
|
self._transport.close()
|
|
self._transport = None
|
|
|
|
def shutdown(self):
|
|
self.initiateShutdown(
|
|
"EdenClient.shutdown() invoked with no reason by pid=%s uid=%s"
|
|
% (os.getpid(), os.getuid())
|
|
)
|
|
|
|
def initiateShutdown(self, reason):
|
|
"""Helper for stopping the server.
|
|
To swing through the transition from calling the base shutdown() method
|
|
with context to the initiateShutdown() method with a reason, we want to
|
|
try the latter method first, falling back to the old way to handle the
|
|
case where we deploy a newer client while an older server is still
|
|
running on the local system."""
|
|
try:
|
|
super().initiateShutdown(reason)
|
|
except TApplicationException as ex:
|
|
if ex.type == TApplicationException.UNKNOWN_METHOD:
|
|
# Running an older server build, fall back to the old shutdown
|
|
# method with no context
|
|
super().shutdown()
|
|
else:
|
|
raise
|
|
|
|
|
|
def create_thrift_client(eden_dir=None, socket_path=None):
|
|
"""Construct a thrift client to speak to the running eden server
|
|
instance associated with the specified mount point.
|
|
|
|
@return Returns a context manager for EdenService.Client.
|
|
"""
|
|
return EdenClient(eden_dir=eden_dir, socket_path=socket_path)
|