summaryrefslogtreecommitdiff
path: root/tornado_main.py
blob: 7683e85f1b738f5d8a90909c6494c29f9292a4cd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#!/usr/bin/env python

# Run this with
# PYTHONPATH=. DJANGO_SETTINGS_MODULE=testsite.settings
# testsite/tornado_main.py

from tornado.options import options, define, parse_command_line
import django.core.handlers.wsgi
import tornado.httpserver
import tornado.ioloop
import tornado.web
import tornado.wsgi
import os, sys
import json as simplejson
import django

#Gist https://gist.githubusercontent.com/wonderbeyond/d38cd85243befe863cdde54b84505784/raw/ab78419248055333a6bf4a50022311cae9d6596c/graceful_shutdown_tornado_web_server.py

import time
import signal
import logging
from functools import partial

import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web

from tornado.options import define, options

MAX_WAIT_SECONDS_BEFORE_SHUTDOWN = 3

#pid = str(os.getpid())
#f = open(os.environ['SOC_PID'], 'w')
#f.write(pid)
#f.close()

django.setup()
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "R_on_Cloud.settings")

from concurrent.futures import ThreadPoolExecutor
from tornado import gen
from instances import execute_code
import threading
import pwd
from R_on_Cloud.config import TORNADO_IP, TORNADO_PORT

define('port', type=int, default=TORNADO_PORT)
define("ip", default=TORNADO_IP, help="Run on any given IP", type=str)

# Custom settings
from R_on_Cloud.settings import PROJECT_DIR
from django.core.wsgi import get_wsgi_application

#Gist

def sig_handler(server, sig, frame):
    io_loop = tornado.ioloop.IOLoop.instance()

    def stop_loop(deadline):
        now = time.time()
        #if now < deadline:
        if now < deadline and (io_loop._callbacks or io_loop._timeouts):
            logging.info('Waiting for next tick')
            io_loop.add_timeout(now + 1, stop_loop, deadline)
        else:
            io_loop.stop()
            logging.info('Shutdown finally')

    def shutdown():
        logging.info('Stopping Django DB connections...')
        from django.db import connections
        for conn in connections.all():
            conn.close()
        logging.info('Stopping http server')
        server.stop()
        logging.info('Will shutdown in %s seconds ...',
                     MAX_WAIT_SECONDS_BEFORE_SHUTDOWN)
        stop_loop(time.time() + MAX_WAIT_SECONDS_BEFORE_SHUTDOWN)

    logging.warning('Caught signal: %s', sig)
    io_loop.add_callback_from_signal(shutdown)

#End Gist


def run_as_nobody():
    """Runs the current process as nobody."""
    # Set the effective uid and to that of nobody.
    nobody = pwd.getpwnam('nobody')
    os.setegid(nobody.pw_gid)
    os.seteuid(nobody.pw_uid)


# request_count keeps track of the number of requests at hand, it is incremented
# when post method is invoked and decremented before exiting post method in
# class ExecutionHandler.
DEFAULT_WORKERS = 5
request_count = 0

# ThreadPoolExecutor is an Executor subclass that uses a pool of threads to
# execute
# function calls asynchronously.
# It runs numbers of threads equal to DEFAULT_WORKERS in the background.
executor = ThreadPoolExecutor(max_workers=DEFAULT_WORKERS)

# instance_manager function is run at a fixed interval to kill the
# Scilab instances not in use. If the number of user requests is more than the
# count of active Scilab instances, maximum instances defined will be in
# process. Instances will be killed only when their number is more than the user
# requests.

# Whenever django server sends an ajax request,
# the request is handled by the  ExecutionHandler
# post method passes all the parameters received from the ajax call and
# passes it to the submit method of ThreadPoolExecutor class through its object.
# yield is used to gather the output asynchronously in the variable data


class ExecutionHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def post(self):
        global request_count
        request_count += 1
        session_id = self.request.arguments['session_id'][0].decode('UTF-8')
        R_file_id = str(time.time())
        code = self.request.arguments['code'][0].decode('UTF-8')
        data = yield executor.submit(execute_code, code, session_id, R_file_id)
        self.write(data)
        request_count -= 1


def main():
    parse_command_line()
    wsgi_app = tornado.wsgi.WSGIContainer(
        get_wsgi_application())
    tornado_app = tornado.web.Application(
        [
            ('/execute-code', ExecutionHandler),
            ('/static/(.*)', tornado.web.StaticFileHandler,
             {'path': PROJECT_DIR + '/static/'}),
            ('.*', tornado.web.FallbackHandler, dict(fallback=wsgi_app)),
        ], debug=False)
    server = tornado.httpserver.HTTPServer(tornado_app)
    server.listen(options.port, options.ip)


    try:
        #server.start(0)
        tornado.ioloop.IOLoop.instance().start()
    # signal : CTRL + BREAK on windows or CTRL + C on linux
    except KeyboardInterrupt:
        signal.signal(signal.SIGTERM, partial(sig_handler, server))
        signal.signal(signal.SIGQUIT, partial(sig_handler, server))
        sys.exit(0)

#Gist

    logging.info("Exit...")

#End Gist

if __name__ == '__main__':
    main()