Python переменные класса не работают должным образом в приложении Django - PullRequest
0 голосов
/ 07 августа 2020

Я создал представление REST API в Django, чтобы отслеживать, кто в данный момент набирает текст в клиентской программе чата, поэтому я могу уведомить другого пользователя в канале чата с помощью анимированного значка.

На стороне клиента каждые 2 секунды это представление будет получать вызов POST - своего рода «пинг» - до тех пор, пока пользователь печатает. Если пользователь перестает печатать дольше 2 секунд, эти пинги перестают поступать.

На стороне сервера, как вы можете видеть в этом представлении, я храню список current_typists, индексируемый * чата channel_id и typist_id. Этот список содержит 5-секундные таймеры, по одному на машинистку. Каждый раз, когда приходит пинг, таймер продлевается. Если в течение 5 секунд не было пинга, таймер истечет, и другой машинист получит уведомление о том, что набор текста остановлен.

Я сохраняю current_typists как переменную класса, а также typing_lock, который гарантирует, что асинхронный доступ к этим таймерам не будет конфликтовать.

@authentication_classes((TokenAuthentication,))
class ChatUserIsTypingView(APIView):
    
    current_typists = {}
    typing_lock = Lock()

    # Notify the other user silently whether typist_id is typing or not
    def notify_typing_status(self, typist_id, other_id, typing):
        do_the_notification_here()

    # When a timer has elapsed after 5 seconds, remove it.
    def chat_user_stopped_typing(self, channel_id, typist_id, other_id, notify=True):
        self.typing_lock.acquire()
        print("Timer", self.current_typists[(channel_id, typist_id)], "elapsed. REMOVING.")
        self.current_typists.pop((channel_id, typist_id))
        print("AFTER REMOVAL, keys =", self.current_typists.keys())
        if notify:
            self.notify_typing_status(typist_id, other_id, False)
        self.typing_lock.release()
    
    # We receive this 'ping' every two seconds from the client while a user is typing
    def post(self, request, format=None):
        channel_id = request.data['channel_id']
        typist_id = request.user.id
        other_id = request.data['other_id']
    
        self.typing_lock.acquire()
        already_typing = (channel_id, typist_id) in self.current_typists
        if not already_typing:
            print("NO current timer, keys:", self.current_typists.keys())
            self.notify_typing_status(typist_id, other_id, True)
        else:
            print("CANCELING timer", self.current_typists[(channel_id, typist_id)])
            self.current_typists[(channel_id, typist_id)].cancel()
            self.current_typists.pop((channel_id, typist_id))
            print("CANCELED timer, keys:", self.current_typists.keys())
        typing_sample_period = 5
        t = Timer(typing_sample_period, self.chat_user_stopped_typing, args=[channel_id, typist_id, other_id, True])
        self.current_typists[(channel_id, typist_id)] = t
        t.start()
        print("STARTED timer", t, "keys:", self.current_typists.keys())
        self.typing_lock.release()
    
        return Response({}, status=status.HTTP_200_OK)

Возможно, это больше, чем необходимо, но контекст может помочь.

Вот что происходит на практике. Это вроде как работает, но иногда пользователь получает уведомление о том, что набор текста остановлен, прежде чем это действительно произошло. Я снабдил код этими операторами печати, чтобы увидеть, что происходит.

2020-08-06T22:20:05.710264+00:00 app[web.1]: NO current timer, keys: dict_keys([])
2020-08-06T22:20:06.502792+00:00 heroku[router]: at=info method=POST path="/notifications/chat_user_is_typing/" host=ridehare.herokuapp.com request_id=ae3f9e37-0bad-4333-8294-f4e639c4c970 fwd="172.91.141.66" dyno=web.1 connect=1ms service=1067ms status=200 bytes=218 protocol=https
2020-08-06T22:20:06.498288+00:00 app[web.1]: STARTED timer <Timer(Thread-18, started 140708212107008)> keys: dict_keys([('OCsHYT3eiHV0PBbJDCPZ', 51)])
2020-08-06T22:20:06.502453+00:00 app[web.1]: 10.63.103.37 - - [06/Aug/2020:15:20:06 -0700] "POST /notifications/chat_user_is_typing/ HTTP/1.1" 200 2 "-" "RideHare/0.1 (com.ridehare.RideHare; build:4716; iOS 13.6.0) Alamofire/5.2.2"
2020-08-06T22:20:08.915502+00:00 app[web.1]: NO current timer, keys: dict_keys([])
2020-08-06T22:20:09.641035+00:00 app[web.1]: STARTED timer <Timer(Thread-18, started 140708161779456)> keys: dict_keys([('OCsHYT3eiHV0PBbJDCPZ', 51)])
2020-08-06T22:20:09.645318+00:00 app[web.1]: 10.63.103.37 - - [06/Aug/2020:15:20:09 -0700] "POST /notifications/chat_user_is_typing/ HTTP/1.1" 200 2 "-" "RideHare/0.1 (com.ridehare.RideHare; build:4716; iOS 13.6.0) Alamofire/5.2.2"
2020-08-06T22:20:09.648912+00:00 heroku[router]: at=info method=POST path="/notifications/chat_user_is_typing/" host=ridehare.herokuapp.com request_id=c490aeda-1851-4f1e-b856-2c7309b51164 fwd="172.91.141.66" dyno=web.1 connect=2ms service=913ms status=200 bytes=218 protocol=https

Как вы можете видеть на этом прогоне, при первом эхо-запросе я запускаю таймер и сохраняю его в current_typists. Но при втором пинге таймеров там вообще нет. (Этот второй оператор «НЕТ текущего таймера» является неожиданностью.) Из того, что я прочитал, переменные класса в Python по сути являются статическими c переменными - по одной на класс - но это ведет себя так, как если бы их было несколько. .

Возможно, это свидетельство того, что приложение Django выполняется более чем в одном процессе? В любом случае, как мне лучше решить эту проблему?

...