mod_wsgi + ctypes causes segmentation fault - python

I have a python function that uses ctypes to call some c code
The python code looks like:
import numpy as np
from ctypes import *
dll=cdll.LoadLibrary("./test.so")
def callTestFunction():
out=np.zeros(shape=(10),dtype=np.float32)
dll.testFunction(out.ctypes.data,10)
return out
And the c code looks like
void testFunction(float values[], int l){
for(int i=0;i<l;i++){
values[i]=1;
}
}
This code works fine if I run python and call the function.
But when I import the same code and call the function inside of mod_wsgi, I get a segmentation fault.
I have already set WSGIApplicationGroup %{GLOBAL} and if I capture the segfault in httpd using gdb, it says
#0 0x00007fffc9fa9bad in testFunction (values=0x563e93b0, l=10) at test.c:63
63 values[i]=1;
(gdb) print *values#10
Cannot access memory at address 0x563e93b0
My guess is Apache is enforcing some kind of memory boundary between my python code and my c library? Does anyone have a solution for this, or know of a better way to return an array from c to python inside of mod_wsgi?
Update:
I added print statements to python and c to print out
sizeof(c_void_p), out.ctypes.data, and values.
in ipython
out.ctypes.data 0x23dde40
sizeof(c_void_p) 8
values (in c): 23dde40
in apache
[Sun May 10 17:37:01.647440 2020] [wsgi:error] [pid 7101] [client 127.0.0.1:60346] out.ctypes.data 0x55555645f7c0
[Sun May 10 17:37:01.647592 2020] [wsgi:error] [pid 7101] [client 127.0.0.1:60346] sizeof(c_void_p) 8
...
(gdb) p values
$1 = (float *) 0x5645f7c0
So there is a difference in Apache! 0x55555645f7c0 vs 0x5645f7c0
If I look at the correct memory location in GDB, it looks promising!
(gdb) p *0x55555645f7c0#40
$2 = {0 <repeats 40 times>}
Turns out I need to cast out.ctypes.data as c_void_p!
Corrected python code:
import numpy as np
from ctypes import *
dll=cdll.LoadLibrary("./test.so")
def callTestFunction():
out=np.zeros(shape=(10),dtype=np.float32)
dll.testFunction(c_void_p(out.ctypes.data),c_int(10))
return out
I still have no idea why this works in ipython, but not Apache.

Related

Base 64 decode with Python on XAMPP

I'm trying to bring on my local server (XAMPP), a script that's working on my VPS server (Linux CentOS7).
On XAMPP, I call the Python script wit PHP, something like:
$hotel = array("Name"=>$_POST["NAME"]
,..
);
$param = escapeshellcmd(base64_encode(json_encode($hotel)));
$result = shell_exec('python C:\xampp\htdocs\bounce.py $param');
$obj = json_decode($result);
The Python script is something like:
#! /Users/<user>/AppData/Local/Programs/Python/Python37/python.exe
import sys
import json
import base64
content = json.loads(base64.b64decode(sys.argv[1]))
print(json.dumps(content))
The returned JSON string is NULL
This is the Apache error:
[php:warn] [pid 11176:tid 1884] [client ::1:55182] PHP Warning: Attempt to read property "Name" on null in C:\\xampp\\htdocs\\hotel_results.php on line 46, referer: http://localhost/
Updated Django with no results
If the problem is caused by not reading the "$param" variable due to single quotes, replacing the line of
$result = shell_exec('python C:\xampp\htdocs\bounce.py $param');
with
$result = shell_exec("python C:\\xampp\\htdocs\\bounce.py $param");
could help. worth a try.

calling a python function from flask app

I am developing an app that runs (always - it is a controller for a heat pump system) in python and I use flask to provide a user interface to controll the app.
The flask app has different control items, for instance buttons to turn the system on or off.
I am trying to execute a specific function from a python module in response to a "click" on a button (the final goal is to change a value in an mmap resource that will be read in another module to change the state of the system).
In the flask app I have something like:
#app.route('/cntr_hpauto',methods=['GET','POST'])
#basic_auth.required
def cntr_hpauto():
manage_globals.set_from_web()
return render_template('control.html',cur_hp_mode="auto")
However, this generates an "internal server error'
The complete flask app is (manage_globals is the *.py file I want to import and that contains the function I want to call):
from flask import Flask, request, render_template
from flask_basicauth import BasicAuth
import sys
import os
import mmap
import manage_globals
app = Flask(__name__)
app.config['BASIC_AUTH_USERNAME'] = '***'
app.config['BASIC_AUTH_PASSWORD'] = '***'
basic_auth = BasicAuth(app)
#app.route('/')
def splash():
return render_template('splash.html')
#app.route('/dashboard', methods=['GET','POST'])
#basic_auth.required
def dashboard():
return render_template('dashboard.html')
#app.route('/control',methods=['GET','POST'])
#basic_auth.required
def control():
return render_template('control.html',cur_hp_mode="none")
#app.route('/cntr_hpauto',methods=['GET','POST'])
#basic_auth.required
def cntr_hpauto():
manage_globals.set_from_web()
return render_template('control.html',cur_hp_mode="auto")
#app.route('/cntr_hpon',methods=['GET','POST'])
#basic_auth.required
def cntr_hpon():
return render_template('control.html',cur_hp_mode="on")
#app.route('/cntr_hpoff',methods=['GET','POST'])
#basic_auth.required
def cntr_hpoff():
return render_template('control.html',cur_hp_mode="off")
if __name__ == '__main__':
app.run(ssl_context=('/home/groenhol/certs/groenhol.pem', '/home/groenhol/certs/groenhol.key'))
And the module (example, only writing the map file to a logfile) is:
# 14/08/2017 henk witte / groenholland
# part of geotech project, ann controller dual source heat pump
# this module maintains the global database with mmap
import mmap
""" the mmap file is position dependent!
use readlines and split
line 1: heatpump auto/on/off
line 2: userpump off
line 3: srcselect air
"""
def init_conf_file():
dummy="a"
def set_from_web():
with open("geotech.conf", "r+b") as f:
mm = mmap.mmap(f.fileno(), 0)
for line in iter(mm.readline, b''):
with open("globals.log","ab") as f2:
f2.write(line)
f2.close()
mm.close()
if __name__ == '__main__':
init_conf_file()
The flask app runs fine without the function call, the module I import by itself runs fine as well.
Any help much appreciated!
Henk
As suggested by Kevin Pasquarella I added app.debug = true. However, as the error occurs when apache is loadin the main splash page already (apache server error) this did not help. But I then looked at the apache error log:
[Tue Aug 15 21:33:14.638580 2017] [mpm_event:notice] [pid 959:tid 3067240448] AH00489: Apache/2.4.18 (Ubuntu) OpenSSL/1.0.2g mod_wsgi/4.5.17 Python/3.4 configured -- resuming normal operations
[Tue Aug 15 21:33:14.639152 2017] [core:notice] [pid 959:tid 3067240448] AH00094: Command line: '/usr/sbin/apache2'
[Tue Aug 15 21:33:19.825211 2017] [wsgi:error] [pid 2461:tid 3031819312] [remote 192.168.178.85:9676] mod_wsgi (pid=2461): Target WSGI script '/home/groenhol/py_control/ui/webapp/main.wsgi' cannot be loaded as Python module.
[Tue Aug 15 21:33:19.826502 2017] [wsgi:error] [pid 2461:tid 3031819312] [remote 192.168.178.85:9676] mod_wsgi (pid=2461): Exception occurred processing WSGI script '/home/groenhol/py_control/ui/webapp/main.wsgi'.
[Tue Aug 15 21:33:19.967421 2017] [wsgi:error] [pid 2461:tid 3031819312] [remote 192.168.178.85:9676] Traceback (most recent call last):
[Tue Aug 15 21:33:19.970377 2017] [wsgi:error] [pid 2461:tid 3031819312] [remote 192.168.178.85:9676] File "/home/groenhol/py_control/ui/webapp/main.wsgi", line 4, in <module>
[Tue Aug 15 21:33:19.970581 2017] [wsgi:error] [pid 2461:tid 3031819312] [remote 192.168.178.85:9676] from main import app as application
[Tue Aug 15 21:33:19.971031 2017] [wsgi:error] [pid 2461:tid 3031819312] [remote 192.168.178.85:9676] File "/home/groenhol/py_control/ui/webapp/main.py", line 41
I then searched for mod_wsgi cannot be loaded as python module
Answers indicate there is a difference between the python version I am using (3.4) and the wsgi version.
So I checked the wsgi version in /etc/apache2/mods-enabled/mod-wsgi.load:
LoadModule wsgi_module "/home/groenhol/miniconda3/lib/python3.4/site-packages/mod_wsgi/server/mod_wsgi-py34.cpython-34m.so"
WSGIPythonHome "/home/groenhol/miniconda3"
So seems to use python 3.4 version.
To make sure I use ldd as I found during the search:
groenhol#arm:~/mod_wsgi-4.5.15$ ldd LoadModule wsgi_module "/home/groenhol/miniconda3/lib/python3.4/site-packages/mod_wsgi/server/mod_wsgi-py34.cpython-34m.so"
LoadModule:
ldd: ./LoadModule: No such file or directory
wsgi_module:
ldd: ./wsgi_module: No such file or directory
/home/groenhol/miniconda3/lib/python3.4/site-packages/mod_wsgi/server/mod_wsgi-py34.cpython-34m.so:
linux-vdso.so.1 => (0xbee90000)
libpython3.4m.so.1.0 => /home/groenhol/miniconda3/lib/libpython3.4m.so.1.0 (0xb6d40000)
libpthread.so.0 => /lib/arm-linux-gnueabihf/libpthread.so.0 (0xb6d0f000)
libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0xb6c23000)
/lib/ld-linux-armhf.so.3 (0x7f64d000)
libdl.so.2 => /lib/arm-linux-gnueabihf/libdl.so.2 (0xb6c10000)
libutil.so.1 => /lib/arm-linux-gnueabihf/libutil.so.1 (0xb6bfd000)
libm.so.6 => /lib/arm-linux-gnueabihf/libm.so.6 (0xb6b85000)
libgcc_s.so.1 => /lib/arm-linux-gnueabihf/libgcc_s.so.1 (0xb6b5c000)
groenhol#arm:~/mod_wsgi-4.5.15$ WSGIPythonHome "/home/groenhol/miniconda3"
-bash: WSGIPythonHome: command not found
As far as I can tell (http://modwsgi.readthedocs.io/en/develop/user-guides/checking-your-installation.html#python-shared-library) this seems OK?
Ok, so next step?
The code:
def set_from_web():
with open("geotech.conf", "r+b") as f:
mm = mmap.mmap(f.fileno(), 0)
for line in iter(mm.readline, b''):
with open("globals.log","ab") as f2:
f2.write(line)
f2.close()
mm.close()
is going to be a problem because you are using a relative path name to files.
The current working directory of the process will not be where your code is and also will not be writable to the Apache user. You need to use absolute paths and ensure the Apache user has write permission to files.
See:
http://modwsgi.readthedocs.io/en/develop/user-guides/application-issues.html#application-working-directory
http://modwsgi.readthedocs.io/en/develop/user-guides/application-issues.html#access-rights-of-apache-user
The solution turned out to be pretty trivial: the mod_wsgi does not like if you ident by spaces and tabs. I changed all idents to tabs and then the code runs!
I found this out by changing the code to something very simple, just returning a string and printing that on the web page created by the flask template. Then I could see the wsgi fault in the apache log. In the full code other faults were occuring making it difficult to find out what exactly caused the error.
I also took care of the comment made by Graham Dumpleton (that apache cannot write to the directory), I created a shared directory (/home/py_shared) which I added to the www-data group (both the python user and apache are member of that group). I then set the group of the folder to www-data and used chmod g+w py_shared and chmod g+s py_shared to set the correct permissions.
This topic is discussed on several pages, e.g.:
https://unix.stackexchange.com/questions/154776/create-files-that-both-www-data-and-myuser-can-edit
THANKS for all your suggestions!

Apache hangs with Django / Matplotlib app

new StackOverflow user here. I need help with an Apache freezing problem. I have a WAMPServer setup on Win 7 64-bit and am working with python / django / mysql / mod_wsgi / matplotlib, experimenting with dynamically rendered images. I am using Apache to serve static files.
I am trying to plot data from a MySQL database. My views.py file is below. When I invoke the function "view_Stats" by visiting the appropriate web page, this calls the "CreateFig" function to create and save .png files to a directory that are subsequently served by Apache. It works fine initially, but it seems as if a maximum of 8 calls can be made to the "CreateFig" function before Apache just hangs. I have to restart Apache at that point, but it takes a while (minutes) for it to restart.
Looking at the Apache error logs (see below) shows an error related to Apache child processes that requires Apache to force it to terminate. I suspect some sort of memory leak / error, but I'm pretty new at this and can't troubleshoot well; I've Googled this and looked around on StackOverflow, no joy.
Any help would be appreciated!
[Tue Mar 11 17:01:07.550093 2014] [core:notice] [pid 2820:tid 404] AH00094: Command line: 'c:\\wamp\\bin\\apache\\apache2.4.4\\bin\\httpd.exe -d C:/wamp/bin/apache/Apache2.4.4'
[Tue Mar 11 17:01:07.551093 2014] [mpm_winnt:notice] [pid 2820:tid 404] AH00418: Parent: Created child process 3528
[Tue Mar 11 17:01:07.856093 2014] [mpm_winnt:notice] [pid 3528:tid 324] AH00354: Child: Starting 150 worker threads.
[Tue Mar 11 17:04:53.233893 2014] [mpm_winnt:notice] [pid 2820:tid 404] AH00422: Parent: Received shutdown signal -- Shutting down the server.
[Tue Mar 11 17:05:23.248293 2014] [mpm_winnt:notice] [pid 2820:tid 404] AH00431: Parent: Forcing termination of child process 3528
The Code from views.py is below:
from django.contrib import auth
from django.contrib.auth.models import User, Group
from django.core.context_processors import csrf
from django.shortcuts import render_to_response
from django.http import Http404, HttpResponseRedirect
from rwjcnlab import settings
from clientele.models import UserProfile
from reports.models import EEG, LTM, EMU, AEEG
import os, datetime
import numpy
from pylab import *
import matplotlib.pyplot as plt; plt.rcdefaults()
import matplotlib.pyplot as plt
import gc
# CREATE VIEWS HERE
def view_Stats(request):
UID = UserProfile.objects.get(user_id = request.user.id)
StatsEEG, StatsLTM, StatsAEEG, StatsEMU, start_date = ReportNumbers(UID.id)
# Create figures
CreateFig(StatsEEG, 300, 50, 'EEG', 'b')
CreateFig(StatsLTM, 100, 10, 'LTM', 'r')
CreateFig(StatsAEEG, 15, 3, 'AEEG', 'y')
CreateFig(StatsEMU, 25, 5, 'EMU', 'c')
return render_to_response('view_Stats.html', {
'StatsEEG': StatsEEG,
'StatsLTM': StatsLTM,
'StatsAEEG': StatsAEEG,
'StatsEMU': StatsEMU,
'start_date': start_date,
'user': request.user,
})
def CreateFig(Stats, ymax, yinc, figname, c):
nAll = tuple(x[1] for x in Stats)
nUser = tuple(x[2] for x in Stats)
xlabels = tuple(x[0].strftime("%b%y") for x in Stats)
ind = numpy.arange(len(xlabels)-1.4,-0.4,-1) # the x locations for the groups
width = 0.8 # the width of the bars: can also be len(x) sequence
plt.ioff()
fig = plt.figure(figsize=(10, 5), dpi=72, facecolor='w', edgecolor='k')
p1 = plt.bar(ind, nAll[1:], width, color=c)
p2 = plt.bar(ind, nUser[1:], width, color='g')
plt.title(figname+' Volumes at RWJUH')
plt.xticks(ind+width/2., xlabels[1:])
plt.yticks(numpy.arange(0,ymax,yinc))
plt.legend( (p1[0], p2[0]), ('Total', 'User') )
plt.savefig(os.path.join(settings.BASE_DIR, 'static/'+figname+'.png'))
fig.clf()
plt.close(fig)
gc.collect()
return
This is likely because you're trying to connect to a (presumably non-existent) X-server when you use matplotlib. If you do have X running on your webserver, you probably still want to avoid using an interactive backend for matplotlib
(Edit: Just saw that you're on windows. Obviously, it's not that mattplotlib is trying to connect to an X-server when run on Windows, but I'd be willing to bet that your problem is still related to using an interactive backend and matplotlib trying to connect to the graphical display.)
If you want to use matplotlib without interactive plots (i.e. without needing an X-server), then you need to explicitly use a non-interactive backend. (e.g. Agg, pdf, etc)
First off, remove from pylab import *. That's a really bad idea for a huge number of reasons (hint, min and max aren't what you think they are, among other things). Also, you don't seem to be using it. You're already accessing matplotlib functionality through the pyplot interface and numpy though the numpy namespace.
Next, before you do import matplotlib.pyplot as plt (or before you do from pylab import * if you decide not to remove it), do:
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot # etc...
Now matplotlib won't try to connect to the X display everytime you make a new figure.

Ctrl-C crashes Python after importing scipy.stats

I'm running 64-bit Python 2.7.3 on Win7 64-bit. I can reliably crash the Python interpreter by doing this:
>>> from scipy import stats
>>> import time
>>> time.sleep(3)
and pressing Control-C during the sleep. A KeyboardInterrupt is not raised; the interpreter crashes. The following is printed:
forrtl: error (200): program aborting due to control-C event
Image PC Routine Line Source
libifcoremd.dll 00000000045031F8 Unknown Unknown Unknown
libifcoremd.dll 00000000044FC789 Unknown Unknown Unknown
libifcoremd.dll 00000000044E8583 Unknown Unknown Unknown
libifcoremd.dll 000000000445725D Unknown Unknown Unknown
libifcoremd.dll 00000000044672A6 Unknown Unknown Unknown
kernel32.dll 0000000077B74AF3 Unknown Unknown Unknown
kernel32.dll 0000000077B3F56D Unknown Unknown Unknown
ntdll.dll 0000000077C73281 Unknown Unknown Unknown
This makes it impossible to interrupt long-running scipy calculations.
Googling for "forrtl" and the like, I see suggestions that this kind of problem is due to use of a Fortran library that overrides Ctrl-C handling. I don't see a bug on the Scipy trackerbut given that Scipy is a library for use with Python, I would consider this a bug. It breaks Python's handling of Ctrl-C. Is there any workaround for this?
Edit: Following #cgohlke's suggestion I tried to add my own handler after importing scipy. This question about a related issue shows that adding a signal handler doesn't work. I tried using the Windows API SetConsoleCtrlHandler function via pywin32:
from scipy import stats
import win32api
def doSaneThing(sig, func=None):
print "Here I am"
raise KeyboardInterrupt
win32api.SetConsoleCtrlHandler(doSaneThing, 1)
After this, hitting Ctrl-C prints "Here I am", but Python still crashes with the forrtl error. Sometimes I also get a message saying "ConsoleCtrlHandler function failed", which quickly disappears.
If I run this in IPython, I can see a normal Python KeyboardInterrupt traceback before the forrtl error. I also see a normal Python traceback followed by the forrtl error if I raise some other error instead of KeyboardInterrupt (e.g., ValueError):
ValueError Traceback (most recent call last)
<ipython-input-1-08defde66fcb> in doSaneThing(sig, func)
3 def doSaneThing(sig, func=None):
4 print "Here I am"
----> 5 raise ValueError
6 win32api.SetConsoleCtrlHandler(doSaneThing, 1)
ValueError:
forrtl: error (200): program aborting due to control-C event
[etc.]
It seems that whatever the underlying handler is doing, it's not just trapping Ctrl-C directly, but is reacting to the error condition (ValueError) and crashing itself. Is there any way to eliminate this?
Here's a variation on your posted solution that may work. Maybe there's a better way to solve this problem -- or maybe even avoid it all together by setting an environment variable that tells the DLL to skip installing a handler. Hopefully this helps until you find a better way.
Both the time module (lines 868-876) and _multiprocessing module (lines 312-321) call SetConsoleCtrlHandler. In the case of the time module, its console control handler sets a Windows event, hInterruptEvent. For the main thread, time.sleep waits on this event via WaitForSingleObject(hInterruptEvent, ul_millis), where ul_millis is the number of milliseconds to sleep unless interrupted by Ctrl+C. Since the handler that you've installed returns True, the time module's handler never gets called to set hInterruptEvent, which means sleep cannot be interrupted.
I tried using imp.init_builtin('time') to reinitialize the time module, but apparently SetConsoleCtrlHandler ignores the 2nd call. It seems the handler has to be removed and then reinserted. Unfortunately, the time module doesn't export a function for that. So, as a kludge, just make sure you import the time module after you install your handler. Since importing scipy also imports time, you need to pre-load libifcoremd.dll using ctypes to get the handlers in the right order. Finally, add a call to thread.interrupt_main to make sure Python's SIGINT handler gets called[1].
For example:
import os
import imp
import ctypes
import thread
import win32api
# Load the DLL manually to ensure its handler gets
# set before our handler.
basepath = imp.find_module('numpy')[1]
ctypes.CDLL(os.path.join(basepath, 'core', 'libmmd.dll'))
ctypes.CDLL(os.path.join(basepath, 'core', 'libifcoremd.dll'))
# Now set our handler for CTRL_C_EVENT. Other control event
# types will chain to the next handler.
def handler(dwCtrlType, hook_sigint=thread.interrupt_main):
if dwCtrlType == 0: # CTRL_C_EVENT
hook_sigint()
return 1 # don't chain to the next handler
return 0 # chain to the next handler
win32api.SetConsoleCtrlHandler(handler, 1)
>>> import time
>>> from scipy import stats
>>> time.sleep(10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyboardInterrupt
[1] interrupt_main calls PyErr_SetInterrupt. This trips Handlers[SIGINT] and calls Py_AddPendingCall to add checksignals_witharg. In turn this calls PyErr_CheckSignals. Since Handlers[SIGINT] is tripped, this calls Handlers[SIGINT].func. Finally, if func is signal.default_int_handler, you'll get a KeyboardInterrupt exception.
Setting the environment variable FOR_DISABLE_CONSOLE_CTRL_HANDLER to 1 seems to fix the issue, but only if it is set before loading offending packages.
import os
os.environ['FOR_DISABLE_CONSOLE_CTRL_HANDLER'] = '1'
[...]
EDIT: While Ctrl+C doesn't crash python anymore, it also fails to stop the current calculation.
I have been able to get a half-workaround by doing this:
from scipy import stats
import win32api
def doSaneThing(sig, func=None):
return True
win32api.SetConsoleCtrlHandler(doSaneThing, 1)
Returning true in the handler stops the chain of handlers so that the meddling Fortran handler is no longer called. However, this workaround is only partial, for two reasons:
It does not actually raise a KeyboardInterrupt, meaning that I can't react to it in Python code. It just drops me back to the prompt.
It doesn't fully interrupt things in the way that Ctrl-C normally does in Python. If in a fresh Python session I do a time.sleep(3) and hit Ctrl-C, the sleep is immediately aborted and I get a KeyboardInterrupt. With the above workaround, the sleep is not aborted, and control returns to the prompt only after the sleep time is up.
Nonetheless, this is still better than crashing the whole session. To me this raises the question of why SciPy (and any other Python libraries that rely on these Intel libraries) don't do this themselves.
I'm leaving this answer unaccepted in the hope that someone can provide a real solution or workaround. By "real" I mean that pressing Ctrl-C during a long-running SciPy calculation should work just like it does when SciPy is not loaded. (Note that this doesn't mean it has to work immediately. Non-SciPy calculations like plain Python sum(xrange(100000000)) may not immediately abort on Ctrl-C, but at least when they do, they raise a KeyboardInterrupt.)
Here's code to patch the dll to remove the call that installs the Ctrl-C handler:
import os
import os.path
import imp
import hashlib
basepath = imp.find_module('numpy')[1]
ifcoremd = os.path.join(basepath, 'core', 'libifcoremd.dll')
with open(ifcoremd, 'rb') as dll:
contents = dll.read()
m = hashlib.md5()
m.update(contents)
patch = {'7cae928b035bbdb90e4bfa725da59188': (0x317FC, '\xeb\x0b'),
'0f86dcd44a1c2e217054c50262f727bf': (0x3fdd9, '\xeb\x10')}[m.hexdigest()]
if patch:
contents = bytearray(contents)
contents[patch[0]:patch[0] + len(patch[1])] = patch[1]
with open(ifcoremd, 'wb') as dll:
dll.write(contents)
else:
print 'Unknown dll version'
EDIT: Here's how I added a patch for the x64. Run python.exe in the debugger, and set a breakpoint for SetConsoleCtrlHandler until you get to the call you want to patch out:
Microsoft (R) Windows Debugger Version 6.12.0002.633 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.
CommandLine: .\venv\Scripts\python.exe
...
0:000> .symfix
0:000> bp kernel32!SetConsoleCtrlHandler
0:000> g
Breakpoint 0 hit
KERNEL32!SetConsoleCtrlHandler:
00007ffc`c25742f0 ff252af00400 jmp qword ptr [KERNEL32!_imp_SetConsoleCtrlHandler (00007ffc`c25c3320)] ds:00007ffc`c25c3320={KERNELBASE!SetConsoleCtrlHandler (00007ffc`bfa12e10)}
0:000> k 5
Child-SP RetAddr Call Site
00000000`007ef7a8 00000000`71415bb4 KERNEL32!SetConsoleCtrlHandler
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\WINDOWS\SYSTEM32\python27.dll -
00000000`007ef7b0 00000000`7035779f MSVCR90!signal+0x17c
00000000`007ef800 00000000`70237ea7 python27!PyOS_getsig+0x3f
00000000`007ef830 00000000`703546cc python27!Py_Main+0x21ce7
00000000`007ef880 00000000`7021698c python27!Py_InitializeEx+0x40c
0:000> g
Python 2.7.11 (v2.7.11:6d1b6a68f775, Dec 5 2015, 20:40:30) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy
...
Breakpoint 0 hit
KERNEL32!SetConsoleCtrlHandler:
00007ffc`c25742f0 ff252af00400 jmp qword ptr [KERNEL32!_imp_SetConsoleCtrlHandler (00007ffc`c25c3320)] ds:00007ffc`c25c3320={KERNELBASE!SetConsoleCtrlHandler (00007ffc`bfa12e10)}
0:000> k 5
Child-SP RetAddr Call Site
00000000`007ec308 00000000`7023df6e KERNEL32!SetConsoleCtrlHandler
00000000`007ec310 00000000`70337877 python27!PyTime_DoubleToTimet+0x10ee
00000000`007ec350 00000000`7033766d python27!PyImport_IsScript+0x4f7
00000000`007ec380 00000000`70338bf2 python27!PyImport_IsScript+0x2ed
00000000`007ec3b0 00000000`703385a9 python27!PyImport_ImportModuleLevel+0xc82
0:000> g
...
>>> import scipy.stats
...
Breakpoint 0 hit
KERNEL32!SetConsoleCtrlHandler:
00007ffc`c25742f0 ff252af00400 jmp qword ptr [KERNEL32!_imp_SetConsoleCtrlHandler (00007ffc`c25c3320)] ds:00007ffc`c25c3320={KERNELBASE!SetConsoleCtrlHandler (00007ffc`bfa12e10)}
0:000> k 5
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Users\kevin\Documents\\venv\lib\site-packages\numpy\core\libifcoremd.dll -
Child-SP RetAddr Call Site
00000000`007ed818 00007ffc`828309eb KERNEL32!SetConsoleCtrlHandler
00000000`007ed820 00007ffc`828dfa44 libifcoremd!GETEXCEPTIONPTRSQQ+0xdb
00000000`007ed880 00007ffc`828e59d7 libifcoremd!for_lt_ne+0xc274
00000000`007ed8b0 00007ffc`828e5aff libifcoremd!for_lt_ne+0x12207
00000000`007ed8e0 00007ffc`c292ddc7 libifcoremd!for_lt_ne+0x1232f
0:000> ub 00007ffc`828309eb
libifcoremd!GETEXCEPTIONPTRSQQ+0xbb:
00007ffc`828309cb 00e8 add al,ch
00007ffc`828309cd df040b fild word ptr [rbx+rcx]
00007ffc`828309d0 0033 add byte ptr [rbx],dh
00007ffc`828309d2 c9 leave
00007ffc`828309d3 ff15bf390e00 call qword ptr [libifcoremd!for_lt_ne+0x40bc8 (00007ffc`82914398)]
00007ffc`828309d9 488d0d00efffff lea rcx,[libifcoremd!for_rtl_finish_+0x20 (00007ffc`8282f8e0)]
00007ffc`828309e0 ba01000000 mov edx,1
00007ffc`828309e5 ff158d390e00 call qword ptr [libifcoremd!for_lt_ne+0x40ba8 (00007ffc`82914378)]
We'll patch out the lea instruction with a relative jmp (which is 0xeb followed by the number of bytes to jump)
0:000> ? 00007ffc`828309eb - 00007ffc`828309d9
Evaluate expression: 18 = 00000000`00000012
0:000> f 00007ffc`828309d9 L2 eb 10
Filled 0x2 bytes
0:000> ub 00007ffc`828309eb
libifcoremd!GETEXCEPTIONPTRSQQ+0xbe:
00007ffc`828309ce 040b add al,0Bh
00007ffc`828309d0 0033 add byte ptr [rbx],dh
00007ffc`828309d2 c9 leave
00007ffc`828309d3 ff15bf390e00 call qword ptr [libifcoremd!for_lt_ne+0x40bc8 (00007ffc`82914398)]
00007ffc`828309d9 eb10 jmp libifcoremd!GETEXCEPTIONPTRSQQ+0xdb (00007ffc`828309eb)
00007ffc`828309db 0d00efffff or eax,0FFFFEF00h
00007ffc`828309e0 ba01000000 mov edx,1
00007ffc`828309e5 ff158d390e00 call qword ptr [libifcoremd!for_lt_ne+0x40ba8 (00007ffc`82914378)]
I don't know how the .dll file is mapped in this process, so I'll just search for 0d 00 ef ff ff in the file with a hex editor. It is a unique hit, so we can calculate the location in the .dll to patch.
0:000> db 00007ffc`828309d0
00007ffc`828309d0 00 33 c9 ff 15 bf 39 0e-00 eb 10 0d 00 ef ff ff .3....9.........
00007ffc`828309e0 ba 01 00 00 00 ff 15 8d-39 0e 00 48 8d 0d 0e 9c ........9..H....
00007ffc`828309f0 09 00 e8 09 2e 0a 00 48-8d 0d 32 9f 09 00 e8 fd .......H..2.....
00007ffc`82830a00 2d 0a 00 48 8d 0d ca ee-0e 00 e8 51 90 00 00 85 -..H.......Q....
00007ffc`82830a10 c0 0f 85 88 02 00 00 e8-38 fa 0a 00 ff 15 4e 39 ........8.....N9
00007ffc`82830a20 0e 00 89 c1 e8 d7 2d 0a-00 48 8d 05 f8 be 11 00 ......-..H......
00007ffc`82830a30 45 32 e4 c7 05 0b 4a 13-00 00 00 00 00 41 bd 01 E2....J......A..
00007ffc`82830a40 00 00 00 48 89 05 06 4a-13 00 ff 15 30 39 0e 00 ...H...J....09..
0:000> ? 00007ffc`828309d9 - 00007ffc`828309d0
Evaluate expression: 9 = 00000000`00000009
0:000> ? 00007ffc`828309d9 - 00007ffc`828309d0 + 3FDD0
Evaluate expression: 261593 = 00000000`0003fdd9
0:000>
Ok, I've patched the dll at 0x3fdd9. Let's see what it looks like now:
Microsoft (R) Windows Debugger Version 6.12.0002.633 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.
CommandLine: .\venv\Scripts\python.exe
...
0:000> bp libifcoremd!GETEXCEPTIONPTRSQQ+c9
Bp expression 'libifcoremd!GETEXCEPTIONPTRSQQ+c9' could not be resolved, adding deferred bp
0:000> g
Python 2.7.11 (v2.7.11:6d1b6a68f775, Dec 5 2015, 20:40:30) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import scipy.stats
...
Breakpoint 0 hit
libifcoremd!GETEXCEPTIONPTRSQQ+0xc9:
00007ffc`845909d9 eb10 jmp libifcoremd!GETEXCEPTIONPTRSQQ+0xdb (00007ffc`845909eb)
0:000> u
libifcoremd!GETEXCEPTIONPTRSQQ+0xc9:
00007ffc`845909d9 eb10 jmp libifcoremd!GETEXCEPTIONPTRSQQ+0xdb (00007ffc`845909eb)
00007ffc`845909db 0d00efffff or eax,0FFFFEF00h
00007ffc`845909e0 ba01000000 mov edx,1
00007ffc`845909e5 ff158d390e00 call qword ptr [libifcoremd!for_lt_ne+0x40ba8 (00007ffc`84674378)]
00007ffc`845909eb 488d0d0e9c0900 lea rcx,[libifcoremd!GETHANDLEQQ (00007ffc`8462a600)]
00007ffc`845909f2 e8092e0a00 call libifcoremd!for_lt_ne+0x30 (00007ffc`84633800)
00007ffc`845909f7 488d0d329f0900 lea rcx,[libifcoremd!GETUNITQQ (00007ffc`8462a930)]
00007ffc`845909fe e8fd2d0a00 call libifcoremd!for_lt_ne+0x30 (00007ffc`84633800)
0:000>
So now were are jmping over pushing the arguments on the stack and the function call. So its Ctrl-C handler will not be installed.
Workaround: patch SetControlCtrlHandler
import ctypes
SetConsoleCtrlHandler_body_new = b'\xC2\x08\x00' if ctypes.sizeof(ctypes.c_void_p) == 4 else b'\xC3'
try: SetConsoleCtrlHandler_body = (lambda kernel32: (lambda pSetConsoleCtrlHandler:
kernel32.VirtualProtect(pSetConsoleCtrlHandler, ctypes.c_size_t(1), 0x40, ctypes.byref(ctypes.c_uint32(0)))
and (ctypes.c_char * 3).from_address(pSetConsoleCtrlHandler.value)
)(ctypes.cast(kernel32.SetConsoleCtrlHandler, ctypes.c_void_p)))(ctypes.windll.kernel32)
except: SetConsoleCtrlHandler_body = None
if SetConsoleCtrlHandler_body:
SetConsoleCtrlHandler_body_old = SetConsoleCtrlHandler_body[0:len(SetConsoleCtrlHandler_body_new)]
SetConsoleCtrlHandler_body[0:len(SetConsoleCtrlHandler_body_new)] = SetConsoleCtrlHandler_body_new
try:
import scipy.stats
finally:
if SetConsoleCtrlHandler_body:
SetConsoleCtrlHandler_body[0:len(SetConsoleCtrlHandler_body_new)] = SetConsoleCtrlHandler_body_old
This worked for me:
import os
os.environ['FOR_DISABLE_CONSOLE_CTRL_HANDLER'] = '1'
from scipy.stats import zscore
Try
import os
os.environ['FOR_IGNORE_EXCEPTIONS'] = '1'
import scipy.stats

using Python ladon on Apache, load once for a webservice

as i am not really aware of the underlying strategies or protocols used by Ladon, Webservices and Apache (i am using Ladon and Python with mod_wsgi.so on a Windows Apache server - switched to Ubuntu system)
i wonder if this can be possible to load some ressources for python once, so that exposed methods use these ressources from python code without having to load these ressources again when considering /serving new queries to the web services?
do you have any clue on how to achieve this if possible, or any work around if not ?
typically i am loading some huge dictionaries from files that take too much time to load (I/O) and as it is loaded when receiving each new ladon query, the WS is too slow, i would have like to tell Ladon : "load this when apache start, and made that available to all my python web services/codes as a dictionary during all the time that Apache is running". I will not modify these datas, so i just need to able to read/access them.
best regards
first EDIT : if this could help, looks like on my Ubuntu (i have switched to Ubuntu from my Win config to be more "standard", hope i was right doing this), Apache2 is set in prefork mode rather than MPM, (as suggested by Jakob Simon-Gaarde) readed from :
#: sudo /usr/sbin/apache2 -l
Compiled in modules:
core.c
mod_log_config.c
mod_logio.c
prefork.c
http_core.c
mod_so.c
#: sudo /usr/sbin/apache2 -l | grep MPM
#:
i'm going to check how this can be done, maybe i am also putting some simplified code here, because for now i'm in a noway even with your helpful answers (i can make anything work here :/)
when installing MPM mode, found how to do here: $ sudo apt-get install apache2-mpm-worker
last EDIT:
here is the skeleton of my WS code :
MODEL_DIR = "/home/mydata.file"
import sys
import codecs
import glob
import os
import re
import numpy
from ladon.ladonizer import ladonize
from ladon.types.ladontype import LadonType
from ladon.compat import PORTABLE_STRING
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class LDtest(object):
__metaclass__ = Singleton
modeldir = MODEL_DIR
def __init__(self):
self.load()
def load(self):
modeldir = LDtest.modeldir
self.data = mywrapperfordata.mywrapperfordata(modeldir)
b = datetime.datetime.now()
self.features = self.mywrapperfordata.load() # loading is wrapped here
c = datetime.datetime.now()
print("loading: %s done." % (c-b))
def letsdoit(self, myinput):
return [] # actually main logic ie complex stuff involving accessing to self.features
#ladonize(PORTABLE_STRING, [ PORTABLE_STRING ], rtype = [ PORTABLE_STRING ] )
def ws(self, myinput):
result = self.letsdoit(myinput)
return result
import datetime
a = datetime.datetime.now()
myLDtest = LDtest()
b = datetime.datetime.now()
print("LDtest: %s" % (b-a))
about loading time: from my apache2 log: -notice that module 1 is required and imported by module 2 and also providing as a lonely webservice. It looks like the singleton is not built or not quickly enough?
[Tue Jul 09 11:09:11 2013] [notice] caught SIGTERM, shutting down
[Tue Jul 09 11:09:12 2013] [notice] Apache/2.2.16 (Debian) mod_wsgi/3.3 Python/2.6.6 configured -- resuming normal operations
[Tue Jul 09 11:09:50 2013] [error] Module 4: 0:00:02.885693.
[Tue Jul 09 11:09:51 2013] [error] Module 0: 0:00:03.061020
[Tue Jul 09 11:09:51 2013] [error] Module 1: 0:00:00.026059.
[Tue Jul 09 11:09:51 2013] [error] Module 1: 0:00:00.012517.
[Tue Jul 09 11:09:51 2013] [error] Module 2: 0:00:00.012678.
[Tue Jul 09 11:09:51 2013] [error] Module (dbload): 0:00:00.402387 (22030)
[Tue Jul 09 11:09:54 2013] [error] Module 3: 0:00:00.000036.
[Tue Jul 09 11:13:00 2013] [error] Module 0: 0:00:03.055841
[Tue Jul 09 11:13:01 2013] [error] Module 1: 0:00:00.026215.
[Tue Jul 09 11:13:01 2013] [error] Module 1: 0:00:00.012600.
[Tue Jul 09 11:13:01 2013] [error] Module 2: 0:00:00.012643.
[Tue Jul 09 11:13:01 2013] [error] Module (dbload): 0:00:00.322444 (22030)
[Tue Jul 09 11:13:03 2013] [error] Module 3: 0:00:00.000035.
mod_wsgi launches one or more Python processes upon startup and leaves them running to handle requests. If you load a module or set a global variable, they'll still be there when you handle the next request - however, each Python process has its own separate block of memory, so if you configure mod_wsgi to launch 8 processes and load a 1G dataset, eventually you'll be using 8G of memory. Maybe you should consider using a database?
edit: Thanks Graham :-) So with only one process and multiple threads, you can share one copy of your huge dictionary between all worker threads.
We use Ladon extensively at my work with all our web projects, and I have the priviledge of being able to develop my private project (I am the Ladon developer) and getting payed for it ;-)
Some of our services have very heavy resource consumptions, for instance we have a text-to-speach service that loads around 1Gb of data into memory per supported language, and a wordprediction service that loads around 100Mb per supported language.
mod_wsgi is fine - we use that aswell - What you need to do is make sure that your apache server is compiled as mpm-worker (http://httpd.apache.org/docs/2.2/mod/worker.html). In this configuration your service runs in a multi-threaded environment instead of a multi-process environment. The effect is that you only fire up one interpreter per server process which then runs your service in several underlying threads that share resources. The caveeat is that you have to make sure that your service does not step on it's own toes, meaning you will have to protect global variables and class-static variables shared between service class instances with mutex.acquire()/mutex.release().
Other than that Ladon as a framework is build for multi-threaded environments.
Best regards Jakob Simon-Gaarde

Categories