Skip to content

Commit

Permalink
Merge pull request #2173 from devos50/procescheck_refactor
Browse files Browse the repository at this point in the history
Created a new process checker
  • Loading branch information
whirm committed May 17, 2016
2 parents 0987c1f + 8413b1f commit 30035c5
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 49 deletions.
74 changes: 74 additions & 0 deletions Tribler/Core/Modules/process_checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import os

from Tribler.Core.SessionConfig import SessionStartupConfig


LOCK_FILE_NAME = 'triblerd.lock'


class ProcessChecker(object):
"""
This class contains code to check whether a Tribler process is already running.
"""

def __init__(self):
"""
Check whether a lock file exists in the Tribler directory. If not, create the file. If it exists,
check the PID that is written inside the lock file.
"""
self.already_running = False

self.statedir = SessionStartupConfig().get_state_dir()
self.lock_file_path = os.path.join(self.statedir, LOCK_FILE_NAME)

if os.path.exists(self.lock_file_path):
file_pid = self.get_pid_from_lock_file()
if file_pid == str(os.getpid()):
# Ignore when we find our own PID inside the lock file
self.already_running = False
elif file_pid != os.getpid() and not ProcessChecker.is_pid_running(int(file_pid)):
# The process ID written inside the lock file is old, just remove the lock file and create a new one.
self.remove_lock_file()
self.create_lock_file()
else:
self.already_running = True
else:
self.create_lock_file()

@staticmethod
def is_pid_running(pid):
"""
Check whether a given process ID is currently running. We do this by sending signal 0 to the process
which does not has any effect on the running process.
Source: http://stackoverflow.com/questions/7647167/check-if-a-process-is-running-in-python-in-linux-unix
"""
try:
os.kill(pid, 0)
except OSError:
return False
else:
return True

def create_lock_file(self):
"""
Create the lock file and write the PID in it. We also create the directory structure since the ProcessChecker
might be called before the .Tribler directory has been created.
"""
if not os.path.exists(self.statedir):
os.makedirs(self.statedir)

with open(self.lock_file_path, 'wb') as lock_file:
lock_file.write(str(os.getpid()))

def remove_lock_file(self):
"""
Remove the lock file.
"""
os.unlink(self.lock_file_path)

def get_pid_from_lock_file(self):
"""
Returns the PID from the lock file.
"""
with open(self.lock_file_path, 'rb') as lock_file:
return lock_file.read()
8 changes: 5 additions & 3 deletions Tribler/Main/tribler_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#
# see LICENSE.txt for license information
#
from Tribler.Core.Modules.process_checker import ProcessChecker
from Tribler.Main.Dialogs.NewVersionDialog import NewVersionDialog

try:
Expand Down Expand Up @@ -63,7 +64,6 @@
from Tribler.Main.vwxGUI.TriblerApp import TriblerApp
from Tribler.Main.vwxGUI.TriblerUpgradeDialog import TriblerUpgradeDialog
from Tribler.Utilities.Instance2Instance import Instance2InstanceClient, Instance2InstanceServer
from Tribler.Utilities.SingleInstanceChecker import SingleInstanceChecker
from Tribler.dispersy.util import attach_profiler, call_on_reactor_thread


Expand Down Expand Up @@ -1030,11 +1030,11 @@ def run(params=[""], autoload_discovery=True, use_torrent_search=True, use_chann
params = sys.argv[1:]
try:
# Create single instance semaphore
single_instance_checker = SingleInstanceChecker("tribler")
process_checker = ProcessChecker()

installdir = determine_install_dir()

if not ALLOW_MULTIPLE and single_instance_checker.IsAnotherRunning():
if not ALLOW_MULTIPLE and process_checker.already_running:
statedir = SessionStartupConfig().get_state_dir()

# Send torrent info to abc single instance
Expand Down Expand Up @@ -1063,6 +1063,8 @@ def run(params=[""], autoload_discovery=True, use_torrent_search=True, use_chann

# Niels: No code should be present here, only executed after gui closes

process_checker.remove_lock_file()

logger.info("Client shutting down. Sleeping for a few seconds to allow other threads to finish")
sleep(5)

Expand Down
65 changes: 65 additions & 0 deletions Tribler/Test/Core/Modules/test_process_checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import os
from multiprocessing import Process

from Tribler.Core.Modules.process_checker import ProcessChecker, LOCK_FILE_NAME
from Tribler.Test.test_as_server import AbstractServer


def process_func():
while True:
pass


class TestProcessChecker(AbstractServer):

def tearDown(self, annotate=True):
super(TestProcessChecker, self).tearDown(annotate=annotate)
if self.process:
self.process.terminate()

def setUp(self, annotate=True):
super(TestProcessChecker, self).setUp(annotate=annotate)
self.process = None
self.state_dir = self.getStateDir()

def create_lock_file_with_pid(self, pid):
with open(os.path.join(self.state_dir, LOCK_FILE_NAME), 'wb') as lock_file:
lock_file.write(str(pid))

def test_no_lock_file(self):
"""
Testing whether the process checker returns false when there is no lock file
"""
process_checker = ProcessChecker()
self.assertTrue(os.path.exists(os.path.join(self.state_dir, LOCK_FILE_NAME)))
self.assertFalse(process_checker.already_running)

def test_own_pid_in_lock_file(self):
"""
Testing whether the process checker returns false when it finds its own pid in the lock file
"""
self.create_lock_file_with_pid(os.getpid())
process_checker = ProcessChecker()
self.assertFalse(process_checker.already_running)

def test_other_instance_running(self):
"""
Testing whether the process checker returns true when another process is running
"""
self.process = Process(target=process_func)
self.process.start()

self.create_lock_file_with_pid(self.process.pid)
process_checker = ProcessChecker()
self.assertTrue(process_checker.is_pid_running(self.process.pid))
self.assertTrue(process_checker.already_running)

def test_dead_pid_in_lock_file(self):
"""
Testing whether the process checker returns false when there is a dead pid in the lock file
"""
dead_pid = 134824733
self.create_lock_file_with_pid(dead_pid)
process_checker = ProcessChecker()
self.assertFalse(process_checker.is_pid_running(dead_pid))
self.assertFalse(process_checker.already_running)
41 changes: 0 additions & 41 deletions Tribler/Utilities/SingleInstanceChecker.py

This file was deleted.

22 changes: 17 additions & 5 deletions twisted/twisted/plugins/tribler_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from twisted.python.log import msg
from zope.interface import implements

from Tribler.Core.Modules.process_checker import ProcessChecker
from Tribler.Core.Session import Session
from Tribler.Core.SessionConfig import SessionStartupConfig

Expand All @@ -37,6 +38,11 @@ def __init__(self):
self.session = None
self._stopping = False

def shutdown_process(self, shutdown_message, code=1):
msg(shutdown_message)
reactor.addSystemEventTrigger('after', 'shutdown', os._exit, code)
reactor.stop()

def start_tribler(self):
"""
Main method to startup Tribler.
Expand All @@ -49,19 +55,25 @@ def signal_handler(sig, _):
self.session.shutdown()
msg("Tribler shut down")
reactor.stop()
self.process_checker.remove_lock_file()
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

msg("Starting Tribler")

config = SessionStartupConfig()
config.set_http_api_enabled(True)

# Check if we are already running a Tribler instance
self.process_checker = ProcessChecker()
if self.process_checker.already_running:
self.shutdown_process("Another Tribler instance is already using statedir %s" % config.get_state_dir())
return

msg("Starting Tribler")

self.session = Session(config)
upgrader = self.session.prestart()
if upgrader.failed:
msg("The upgrader failed: .Tribler directory backed up, aborting")
reactor.addSystemEventTrigger('after', 'shutdown', os._exit, 1)
reactor.stop()
self.shutdown_process("The upgrader failed: .Tribler directory backed up, aborting")
else:
self.session.start()
msg("Tribler started")
Expand Down

0 comments on commit 30035c5

Please sign in to comment.