| // Copyright 2010-2014, Google Inc. |
| // All rights reserved. |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| // Session Handler of Mozc server. |
| // Migrated from ipc/interpreter and session/session_manager |
| |
| #include "session/session_handler.h" |
| |
| #include <algorithm> |
| #include <string> |
| #include <vector> |
| |
| #include "base/init.h" |
| #include "base/logging.h" |
| #include "base/port.h" |
| #include "base/process.h" |
| #include "base/singleton.h" |
| #include "base/stopwatch.h" |
| #include "base/util.h" |
| #include "composer/table.h" |
| #include "config/config.pb.h" |
| #include "config/config_handler.h" |
| #include "dictionary/user_dictionary_session_handler.h" |
| #include "dictionary/user_dictionary_storage.pb.h" |
| #include "engine/engine_interface.h" |
| #include "engine/user_data_manager_interface.h" |
| #include "session/commands.pb.h" |
| #include "session/generic_storage_manager.h" |
| #include "session/session.h" |
| #include "session/session_observer_handler.h" |
| #ifndef MOZC_DISABLE_SESSION_WATCHDOG |
| #include "session/session_watch_dog.h" |
| #else // MOZC_DISABLE_SESSION_WATCHDOG |
| // Session watch dog is not aviable from android mozc and nacl mozc for now. |
| // TODO(kkojima): Remove this guard after |
| // enabling session watch dog for android. |
| #endif // MOZC_DISABLE_SESSION_WATCHDOG |
| #include "usage_stats/usage_stats.h" |
| |
| using mozc::usage_stats::UsageStats; |
| |
| DEFINE_int32(timeout, -1, |
| "server timeout. " |
| "if sessions get empty for \"timeout\", " |
| "shutdown message is automatically emitted"); |
| |
| DEFINE_int32(max_session_size, 64, |
| "maximum sessions size. " |
| "if size of sessions reaches to \"max_session_size\", " |
| "oldest session is removed"); |
| |
| DEFINE_int32(create_session_min_interval, 0, |
| "minimum interval (sec) for create session"); |
| |
| DEFINE_int32(watch_dog_interval, 180, |
| "watch dog timer intaval (sec)"); |
| |
| DEFINE_int32(last_command_timeout, 3600, |
| "remove session if it is not accessed for " |
| "\"last_command_timeout\" sec"); |
| |
| DEFINE_int32(last_create_session_timeout, 300, |
| "remove session if it is not accessed for " |
| "\"last_create_session_timeout\" sec " |
| "after create session command"); |
| |
| DEFINE_bool(restricted, false, |
| "Launch server with restricted setting"); |
| |
| namespace mozc { |
| |
| namespace { |
| bool IsApplicationAlive(const session::SessionInterface *session) { |
| #ifndef MOZC_DISABLE_SESSION_WATCHDOG |
| const commands::ApplicationInfo &info = session->application_info(); |
| // When the thread/process's current status is unknown, i.e., |
| // if IsThreadAlive/IsProcessAlive functions failed to know the |
| // status of the thread/process, return true just in case. |
| // Here, we want to kill the session only when the target thread/process |
| // are terminated with 100% probability. Otherwise, it's better to do |
| // nothing to prevent any side effects. |
| #ifdef OS_WIN |
| if (info.has_thread_id()) { |
| return Process::IsThreadAlive( |
| static_cast<size_t>(info.thread_id()), true); |
| } |
| #else // OS_WIN |
| if (info.has_process_id()) { |
| return Process::IsProcessAlive( |
| static_cast<size_t>(info.process_id()), true); |
| } |
| #endif // OS_WIN |
| #else // MOZC_DISABLE_SESSION_WATCHDOG |
| // Currently the process is not available through android mozc and nacl mozc. |
| // TODO(kkojima): remove this guard after |
| // android version supports base/process.cc |
| #endif // MOZC_DISABLE_SESSION_WATCHDOG |
| return true; |
| } |
| |
| bool IsCarrierEmoji(const string &utf8_str) { |
| if (Util::CharsLen(utf8_str) != 1) { |
| return false; |
| } |
| const char *utf8_begin = utf8_str.c_str(); |
| size_t mblen = 0; |
| const uint32 ucs4_val = static_cast<uint32>( |
| Util::UTF8ToUCS4(utf8_begin, utf8_begin + utf8_str.size(), &mblen)); |
| const uint32 kMinEmojiPuaCodePoint = 0xFE000; |
| const uint32 kMaxEmojiPuaCodePoint = 0xFEEA0; |
| return kMinEmojiPuaCodePoint <= ucs4_val && ucs4_val <= kMaxEmojiPuaCodePoint; |
| } |
| } // namespace |
| |
| SessionHandler::SessionHandler(EngineInterface *engine) |
| : is_available_(false), |
| max_session_size_(0), |
| last_session_empty_time_(Util::GetTime()), |
| last_cleanup_time_(0), |
| last_create_session_time_(0), |
| engine_(engine), |
| observer_handler_(new session::SessionObserverHandler()), |
| stopwatch_(new Stopwatch), |
| user_dictionary_session_handler_( |
| new user_dictionary::UserDictionarySessionHandler), |
| table_manager_(new composer::TableManager), |
| request_(new commands::Request) { |
| if (FLAGS_restricted) { |
| VLOG(1) << "Server starts with restricted mode"; |
| // --restricted is almost always specified when mozc_client is inside Job. |
| // The typical case is Startup processes on Vista. |
| // On Vista, StartUp processes are in Job for 60 seconds. In order |
| // to launch new mozc_server inside sandbox, we set the timeout |
| // to be 60sec. Client application hopefully re-launch mozc_server. |
| FLAGS_timeout = 60; |
| FLAGS_max_session_size = 8; |
| FLAGS_watch_dog_interval = 15; |
| FLAGS_last_create_session_timeout = 60; |
| FLAGS_last_command_timeout = 60; |
| } |
| |
| #ifndef MOZC_DISABLE_SESSION_WATCHDOG |
| session_watch_dog_.reset(new SessionWatchDog(FLAGS_watch_dog_interval)); |
| #else // MOZC_DISABLE_SESSION_WATCHDOG |
| // Session watch dog is not aviable from android mozc and nacl mozc for now. |
| // TODO(kkojima): Remove this guard after |
| // enabling session watch dog for android. |
| #endif // MOZC_DISABLE_SESSION_WATCHDOG |
| |
| // allow [2..128] sessions |
| max_session_size_ = max(2, min(FLAGS_max_session_size, 128)); |
| session_map_.reset(new SessionMap(max_session_size_)); |
| |
| if (engine_ == NULL) { |
| return; |
| } |
| |
| // everything is OK |
| is_available_ = true; |
| } |
| |
| SessionHandler::~SessionHandler() { |
| for (SessionElement *element = |
| const_cast<SessionElement *>(session_map_->Head()); |
| element != NULL; element = element->next) { |
| delete element->value; |
| element->value = NULL; |
| } |
| session_map_->Clear(); |
| #ifndef MOZC_DISABLE_SESSION_WATCHDOG |
| if (session_watch_dog_->IsRunning()) { |
| session_watch_dog_->Terminate(); |
| } |
| #else // MOZC_DISABLE_SESSION_WATCHDOG |
| // Session watch dog is not aviable from android mozc and nacl mozc for now. |
| // TODO(kkojima): Remove this guard after |
| // enabling session watch dog for android. |
| #endif // MOZC_DISABLE_SESSION_WATCHDOG |
| } |
| |
| bool SessionHandler::IsAvailable() const { |
| return is_available_; |
| } |
| |
| bool SessionHandler::StartWatchDog() { |
| #ifndef MOZC_DISABLE_SESSION_WATCHDOG |
| if (!session_watch_dog_->IsRunning()) { |
| session_watch_dog_->Start(); |
| } |
| return session_watch_dog_->IsRunning(); |
| #else // MOZC_DISABLE_SESSION_WATCHDOG |
| // Session watch dog is not aviable from android mozc and nacl mozc for now. |
| // TODO(kkojima): Remove this guard after |
| // enabling session watch dog for android. |
| return false; |
| #endif // MOZC_DISABLE_SESSION_WATCHDOG |
| } |
| |
| void SessionHandler::ReloadSession() { |
| observer_handler_->Reload(); |
| ReloadConfig(); |
| } |
| |
| void SessionHandler::ReloadConfig() { |
| const composer::Table *table = table_manager_->GetTable( |
| *request_, config::ConfigHandler::GetConfig()); |
| for (SessionElement *element = |
| const_cast<SessionElement *>(session_map_->Head()); |
| element != NULL; element = element->next) { |
| if (element->value != NULL) { |
| element->value->ReloadConfig(); |
| element->value->SetRequest(request_.get()); |
| element->value->SetTable(table); |
| } |
| } |
| } |
| |
| bool SessionHandler::SyncData(commands::Command *command) { |
| VLOG(1) << "Syncing user data"; |
| engine_->GetUserDataManager()->Sync(); |
| return true; |
| } |
| |
| bool SessionHandler::Shutdown(commands::Command *command) { |
| VLOG(1) << "Shutdown server"; |
| SyncData(command); |
| ReloadSession(); // for saving log_commands |
| is_available_ = false; |
| UsageStats::IncrementCount("ShutDown"); |
| return true; |
| } |
| |
| bool SessionHandler::Reload(commands::Command *command) { |
| VLOG(1) << "Reloading server"; |
| ReloadSession(); |
| engine_->Reload(); |
| RunReloaders(); // call all reloaders defined in .cc file |
| return true; |
| } |
| |
| bool SessionHandler::ClearUserHistory(commands::Command *command) { |
| VLOG(1) << "Clearing user history"; |
| engine_->GetUserDataManager()->ClearUserHistory(); |
| UsageStats::IncrementCount("ClearUserHistory"); |
| return true; |
| } |
| |
| bool SessionHandler::ClearUserPrediction(commands::Command *command) { |
| VLOG(1) << "Clearing user prediction"; |
| engine_->GetUserDataManager()->ClearUserPrediction(); |
| UsageStats::IncrementCount("ClearUserPrediction"); |
| return true; |
| } |
| |
| bool SessionHandler::ClearUnusedUserPrediction(commands::Command *command) { |
| VLOG(1) << "Clearing unused user prediction"; |
| engine_->GetUserDataManager()->ClearUnusedUserPrediction(); |
| UsageStats::IncrementCount("ClearUnusedUserPrediction"); |
| return true; |
| } |
| |
| bool SessionHandler::GetStoredConfig(commands::Command *command) { |
| VLOG(1) << "Getting stored config"; |
| // Ensure the onmemory config is same as the locally stored one |
| // because the local data could be changed by sync. |
| ReloadConfig(); |
| |
| // Use GetStoredConfig instead of GetConfig because GET_CONFIG |
| // command should return raw stored config, which is not |
| // affected by imposed config. |
| if (!config::ConfigHandler::GetStoredConfig( |
| command->mutable_output()->mutable_config())) { |
| LOG(WARNING) << "cannot get config"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool SessionHandler::SetStoredConfig(commands::Command *command) { |
| VLOG(1) << "Setting user config"; |
| if (!command->input().has_config()) { |
| LOG(WARNING) << "config is empty"; |
| return false; |
| } |
| |
| const mozc::config::Config &config = command->input().config(); |
| if (!config::ConfigHandler::SetConfig(config)) { |
| return false; |
| } |
| |
| command->mutable_output()->mutable_config()->CopyFrom(config); |
| Reload(command); |
| UsageStats::IncrementCount("SetConfig"); |
| |
| return true; |
| } |
| |
| bool SessionHandler::SetImposedConfig(commands::Command *command) { |
| VLOG(1) << "Setting imposed config"; |
| if (!command->input().has_config()) { |
| LOG(WARNING) << "config is empty"; |
| return false; |
| } |
| |
| const mozc::config::Config &config = command->input().config(); |
| config::ConfigHandler::SetImposedConfig(config); |
| |
| Reload(command); |
| |
| return true; |
| } |
| |
| bool SessionHandler::SetRequest(commands::Command *command) { |
| VLOG(1) << "Setting client's request"; |
| if (!command->input().has_request()) { |
| LOG(WARNING) << "request is empty"; |
| return false; |
| } |
| |
| request_->CopyFrom(command->input().request()); |
| |
| Reload(command); |
| |
| return true; |
| } |
| |
| bool SessionHandler::InsertToStorage(commands::Command *command) { |
| VLOG(1) << "Insert to generic storage"; |
| if (!command->input().has_storage_entry()) { |
| LOG(WARNING) << "No storage_entry"; |
| return false; |
| } |
| const commands::GenericStorageEntry &storage_entry = |
| command->input().storage_entry(); |
| if (!storage_entry.has_type() || |
| !storage_entry.has_key() || |
| storage_entry.value().size() == 0) { |
| LOG(WARNING) << "storage_entry lacks some fields."; |
| return false; |
| } |
| |
| GenericStorageInterface *storage = |
| GenericStorageManagerFactory::GetStorage(storage_entry.type()); |
| if (!storage) { |
| LOG(WARNING) << "No storage found"; |
| return false; |
| } |
| |
| for (int i = 0; i < storage_entry.value_size(); ++i) { |
| const string &value = storage_entry.value(i); |
| storage->Insert(value, value.c_str()); |
| } |
| |
| if (storage_entry.type() == commands::GenericStorageEntry::EMOJI_HISTORY) { |
| for (int i = 0; i < storage_entry.value_size(); ++i) { |
| if (IsCarrierEmoji(storage_entry.value(i))) { |
| UsageStats::IncrementCount("CommitCarrierEmoji"); |
| } else { |
| UsageStats::IncrementCount("CommitUnicodeEmoji"); |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| bool SessionHandler::ReadAllFromStorage(commands::Command *command) { |
| VLOG(1) << "Read all from storage"; |
| commands::Output *output = command->mutable_output(); |
| if (!command->input().has_storage_entry()) { |
| LOG(WARNING) << "No storage_entry"; |
| return false; |
| } |
| if (!command->input().storage_entry().has_type()) { |
| LOG(WARNING) << "storage_entry lacks type fields."; |
| return false; |
| } |
| |
| commands::GenericStorageEntry::StorageType storage_type = |
| command->input().storage_entry().type(); |
| GenericStorageInterface *storage = |
| GenericStorageManagerFactory::GetStorage(storage_type); |
| if (!storage) { |
| LOG(WARNING) << "No storage found"; |
| return false; |
| } |
| |
| vector<string> result; |
| storage->GetAllValues(&result); |
| output->mutable_storage_entry()->set_type(storage_type); |
| for (size_t i = 0; i < result.size(); ++i) { |
| output->mutable_storage_entry()->add_value(result[i]); |
| } |
| return true; |
| } |
| |
| bool SessionHandler::ClearStorage(commands::Command *command) { |
| VLOG(1) << "Clear storage"; |
| commands::Output *output = command->mutable_output(); |
| if (!command->input().has_storage_entry()) { |
| LOG(WARNING) << "No storage_entry"; |
| return false; |
| } |
| if (!command->input().storage_entry().has_type()) { |
| LOG(WARNING) << "storage_entry lacks type fields."; |
| return false; |
| } |
| |
| commands::GenericStorageEntry::StorageType storage_type = |
| command->input().storage_entry().type(); |
| GenericStorageInterface *storage = |
| GenericStorageManagerFactory::GetStorage(storage_type); |
| if (!storage) { |
| LOG(WARNING) << "No storage found"; |
| return false; |
| } |
| output->mutable_storage_entry()->set_type(storage_type); |
| return storage->Clear(); |
| } |
| |
| bool SessionHandler::EvalCommand(commands::Command *command) { |
| if (!is_available_) { |
| LOG(ERROR) << "SessionHandler is not available."; |
| return false; |
| } |
| |
| bool eval_succeeded = false; |
| stopwatch_->Reset(); |
| stopwatch_->Start(); |
| |
| switch (command->input().type()) { |
| case commands::Input::CREATE_SESSION: |
| eval_succeeded = CreateSession(command); |
| break; |
| case commands::Input::DELETE_SESSION: |
| eval_succeeded = DeleteSession(command); |
| break; |
| case commands::Input::SEND_KEY: |
| eval_succeeded = SendKey(command); |
| break; |
| case commands::Input::TEST_SEND_KEY: |
| eval_succeeded = TestSendKey(command); |
| break; |
| case commands::Input::SEND_COMMAND: |
| eval_succeeded = SendCommand(command); |
| break; |
| case commands::Input::SYNC_DATA: |
| eval_succeeded = SyncData(command); |
| break; |
| case commands::Input::CLEAR_USER_HISTORY: |
| eval_succeeded = ClearUserHistory(command); |
| break; |
| case commands::Input::CLEAR_USER_PREDICTION: |
| eval_succeeded = ClearUserPrediction(command); |
| break; |
| case commands::Input::CLEAR_UNUSED_USER_PREDICTION: |
| eval_succeeded = ClearUnusedUserPrediction(command); |
| break; |
| case commands::Input::GET_CONFIG: |
| eval_succeeded = GetStoredConfig(command); |
| break; |
| case commands::Input::SET_CONFIG: |
| eval_succeeded = SetStoredConfig(command); |
| break; |
| case commands::Input::SET_IMPOSED_CONFIG: |
| eval_succeeded = SetImposedConfig(command); |
| break; |
| case commands::Input::SET_REQUEST: |
| eval_succeeded = SetRequest(command); |
| break; |
| case commands::Input::SHUTDOWN: |
| eval_succeeded = Shutdown(command); |
| break; |
| case commands::Input::RELOAD: |
| eval_succeeded = Reload(command); |
| break; |
| case commands::Input::CLEANUP: |
| eval_succeeded = Cleanup(command); |
| break; |
| case commands::Input::INSERT_TO_STORAGE: |
| eval_succeeded = InsertToStorage(command); |
| break; |
| case commands::Input::READ_ALL_FROM_STORAGE: |
| eval_succeeded = ReadAllFromStorage(command); |
| break; |
| case commands::Input::CLEAR_STORAGE: |
| eval_succeeded = ClearStorage(command); |
| break; |
| case commands::Input::SEND_USER_DICTIONARY_COMMAND: |
| eval_succeeded = SendUserDictionaryCommand(command); |
| break; |
| case commands::Input::NO_OPERATION: |
| eval_succeeded = NoOperation(command); |
| break; |
| default: |
| eval_succeeded = false; |
| } |
| |
| if (eval_succeeded) { |
| UsageStats::IncrementCount("SessionAllEvent"); |
| if (command->input().type() != commands::Input::CREATE_SESSION) { |
| // Fill a session ID even if command->input() doesn't have a id to ensure |
| // that response size should not be 0, which causes disconnection of IPC. |
| command->mutable_output()->set_id(command->input().id()); |
| } |
| } else { |
| command->mutable_output()->set_id(0); |
| command->mutable_output()->set_error_code( |
| commands::Output::SESSION_FAILURE); |
| } |
| |
| if (eval_succeeded) { |
| // TODO(komatsu): Make sre if checking eval_succeeded is necessary or not. |
| observer_handler_->EvalCommandHandler(*command); |
| } |
| |
| stopwatch_->Stop(); |
| UsageStats::UpdateTiming("ElapsedTimeUSec", |
| stopwatch_->GetElapsedMicroseconds()); |
| |
| return is_available_; |
| } |
| |
| session::SessionInterface *SessionHandler::NewSession() { |
| return new session::Session(engine_); |
| } |
| |
| void SessionHandler::AddObserver(session::SessionObserverInterface *observer) { |
| observer_handler_->AddObserver(observer); |
| } |
| |
| bool SessionHandler::SendKey(commands::Command *command) { |
| const SessionID id = command->input().id(); |
| session::SessionInterface **session = session_map_->MutableLookup(id); |
| if (session == NULL || *session == NULL) { |
| LOG(WARNING) << "SessionID " << id << " is not available"; |
| return false; |
| } |
| (*session)->SendKey(command); |
| return true; |
| } |
| |
| bool SessionHandler::TestSendKey(commands::Command *command) { |
| const SessionID id = command->input().id(); |
| session::SessionInterface **session = session_map_->MutableLookup(id); |
| if (session == NULL || *session == NULL) { |
| LOG(WARNING) << "SessionID " << id << " is not available"; |
| return false; |
| } |
| (*session)->TestSendKey(command); |
| return true; |
| } |
| |
| bool SessionHandler::SendCommand(commands::Command *command) { |
| const SessionID id = command->input().id(); |
| session::SessionInterface **session = |
| const_cast<session::SessionInterface **>(session_map_->Lookup(id)); |
| if (session == NULL || *session == NULL) { |
| LOG(WARNING) << "SessionID " << id << " is not available"; |
| return false; |
| } |
| (*session)->SendCommand(command); |
| return true; |
| } |
| |
| bool SessionHandler::CreateSession(commands::Command *command) { |
| // prevent DOS attack |
| // don't allow CreateSession in very short period. |
| const int create_session_minimum_interval = |
| max(0, min(FLAGS_create_session_min_interval, 10)); |
| |
| uint64 current_time = Util::GetTime(); |
| if (last_create_session_time_ != 0 && |
| (current_time - last_create_session_time_) < |
| create_session_minimum_interval) { |
| return false; |
| } |
| |
| last_create_session_time_ = current_time; |
| |
| // if session map is FULL, remove the oldest item from the LRU |
| SessionElement *oldest_element = NULL; |
| if (session_map_->Size() >= max_session_size_) { |
| oldest_element = const_cast<SessionElement *>(session_map_->Tail()); |
| if (oldest_element == NULL) { |
| LOG(ERROR) << "oldest SessionElement is NULL"; |
| return false; |
| } |
| delete oldest_element->value; |
| oldest_element->value = NULL; |
| session_map_->Erase(oldest_element->key); |
| VLOG(1) << "Session is FULL, oldest SessionID " |
| << oldest_element->key << " is removed"; |
| } |
| |
| session::SessionInterface *session = NewSession(); |
| if (session == NULL) { |
| LOG(ERROR) << "Cannot allocate new Session"; |
| return false; |
| } |
| |
| const SessionID new_id = CreateNewSessionID(); |
| SessionElement *element = session_map_->Insert(new_id); |
| element->value = session; |
| command->mutable_output()->set_id(new_id); |
| |
| // The oldes item should be reused |
| DCHECK(oldest_element == NULL || oldest_element == element); |
| |
| if (command->input().has_capability()) { |
| session->set_client_capability(command->input().capability()); |
| } |
| |
| if (command->input().has_application_info()) { |
| session->set_application_info(command->input().application_info()); |
| #ifdef __native_client__ |
| if (command->input().application_info().has_timezone_offset()) { |
| Util::SetTimezoneOffset( |
| command->input().application_info().timezone_offset()); |
| } |
| #endif // __native_client__ |
| } |
| |
| // Ensure the onmemory config is same as the locally stored one |
| // because the local data could be changed by sync. |
| ReloadConfig(); |
| |
| // session is not empty. |
| last_session_empty_time_ = 0; |
| |
| UsageStats::IncrementCount("SessionCreated"); |
| |
| return true; |
| } |
| |
| bool SessionHandler::DeleteSession(commands::Command *command) { |
| DeleteSessionID(command->input().id()); |
| engine_->GetUserDataManager()->Sync(); |
| return true; |
| } |
| |
| // Scan all sessions and find and delete session which is either |
| // (a) The session is not activated for 60min |
| // (b) The session is created but not accessed for 5min |
| // (c) application is already terminated. |
| // Also, if timeout is enabled, shutdown server if there is |
| // no active session and client doesn't send any conversion |
| // request to the server for FLAGS_timeout sec. |
| bool SessionHandler::Cleanup(commands::Command *command) { |
| const uint64 current_time = Util::GetTime(); |
| |
| // suspend/hibernation may happen |
| uint64 suspend_time = 0; |
| #ifndef MOZC_DISABLE_SESSION_WATCHDOG |
| if (last_cleanup_time_ != 0 && |
| session_watch_dog_->IsRunning() && |
| (current_time - last_cleanup_time_) > |
| 2 * session_watch_dog_->interval()) { |
| suspend_time = current_time - last_cleanup_time_ - |
| session_watch_dog_->interval(); |
| LOG(WARNING) << "server went to suspend mode for " |
| << suspend_time << " sec"; |
| } |
| #else // MOZC_DISABLE_SESSION_WATCHDOG |
| // Session watch dog is not aviable from android mozc and nacl mozc for now. |
| // TODO(kkojima): Remove this guard after |
| // enabling session watch dog for android. |
| #endif // MOZC_DISABLE_SESSION_WATCHDOG |
| |
| // allow [1..600] sec. default: 300 |
| const uint64 create_session_timeout = |
| suspend_time + |
| max(1, min(FLAGS_last_create_session_timeout, 600)); |
| |
| // allow [10..7200] sec. default 3600 |
| const uint64 last_command_timeout = |
| suspend_time + |
| max(10, min(FLAGS_last_command_timeout, 7200)); |
| |
| vector<SessionID> remove_ids; |
| for (SessionElement *element = |
| const_cast<SessionElement *>(session_map_->Head()); |
| element != NULL; element = element->next) { |
| session::SessionInterface *session = element->value; |
| if (!IsApplicationAlive(session)) { |
| VLOG(2) << "Application is not alive. Removing: " << element->key; |
| remove_ids.push_back(element->key); |
| } else if (session->last_command_time() == 0) { |
| // no command is exectuted |
| if ((current_time - session->create_session_time()) >= |
| create_session_timeout) { |
| remove_ids.push_back(element->key); |
| } |
| } else { // some commands are executed already |
| if ((current_time - session->last_command_time()) >= |
| last_command_timeout) { |
| remove_ids.push_back(element->key); |
| } |
| } |
| } |
| |
| for (size_t i = 0; i < remove_ids.size(); ++i) { |
| DeleteSessionID(remove_ids[i]); |
| VLOG(1) << "Session ID " << remove_ids[i] << " is removed by server"; |
| } |
| |
| // Sync all data. This is a regression bug fix http://b/3033708 |
| engine_->GetUserDataManager()->Sync(); |
| |
| // timeout is enabled. |
| if (FLAGS_timeout > 0 && |
| last_session_empty_time_ != 0 && |
| (current_time - last_session_empty_time_) |
| >= suspend_time + FLAGS_timeout) { |
| Shutdown(command); |
| } |
| |
| last_cleanup_time_ = current_time; |
| |
| return true; |
| } |
| |
| bool SessionHandler::SendUserDictionaryCommand(commands::Command *command) { |
| if (!command->input().has_user_dictionary_command()) { |
| return false; |
| } |
| user_dictionary::UserDictionaryCommandStatus status; |
| const bool result = user_dictionary_session_handler_->Evaluate( |
| command->input().user_dictionary_command(), &status); |
| if (result) { |
| status.Swap( |
| command->mutable_output()->mutable_user_dictionary_command_status()); |
| } |
| return result; |
| } |
| |
| bool SessionHandler::NoOperation(commands::Command *command) { |
| return true; |
| } |
| |
| // Create Random Session ID in order to make the session id unpredicable |
| SessionID SessionHandler::CreateNewSessionID() { |
| SessionID id = 0; |
| while (true) { |
| Util::GetRandomSequence(reinterpret_cast<char *>(&id), sizeof(id)); |
| // don't allow id == 0, as it is reserved for |
| // "invalid id" |
| if (id != 0 && !session_map_->HasKey(id)) { |
| break; |
| } |
| |
| LOG(WARNING) << "Session ID " << id << " is already used. retry"; |
| } |
| |
| return id; |
| } |
| |
| bool SessionHandler::DeleteSessionID(SessionID id) { |
| session::SessionInterface **session = session_map_->MutableLookup(id); |
| if (session == NULL || *session == NULL) { |
| LOG_IF(WARNING, id != 0) << "cannot find SessionID " << id; |
| return false; |
| } |
| delete *session; |
| |
| session_map_->Erase(id); // remove from LRU |
| |
| // if session gets empty, save the timestamp |
| if (last_session_empty_time_ == 0 && |
| session_map_->Size() == 0) { |
| last_session_empty_time_ = Util::GetTime(); |
| } |
| |
| return true; |
| } |
| } // namespace mozc |