MS RFC 15: Support for thread neutral operation of MapServer/MapScript

Date:

2006/05/01

Author:

Tamas Szekeres

Contact:

szekerest at gmail.com

Status:

Draft

Version:

Not assigned yet

1. Overview

By this time the core MapServer code has become widely used by many of the different application models. The extended usage of MapScript causes the possibility that the execution of the code is carried out by multiple threads, standing a fair chance portions of the code being called by the different threads simultaneously. The threading model of the application may fall beyond the control of the MapServer code, at the worst case even the host process of the application is beyond the programmer's scope. For example a web mapping application may be executed by a pre-existing host process ensuring to serve the user requests and calling the MapServer code by different threads from a pool of threads. The usage of process-wide global variables and resources within MapServer may require the need to serialize of the access to the variable by the multiple threads by using locks. The current approach uses process-wide locks to ensure the safe access to these variables causing performance degradation in multithreaded environments. Currently this behavior may be controlled by the USE_THREAD compilation switch.

2. Purpose

The purpose of this change is to get a significant performance boost by removing "big locks" from the code. It would add a significant improvement of the applications having high number of multiple threads executed simultaneously. This activity could bring in a more feasible support for some application models not really aimed at now like Microsoft ASP.NET. May result in higher clarity of the code by determining the code segments affected by multiple threads.

2. General principles of the solution

This RFC is in a special situation since wide area of the code is to be reviewed and extensive developer support is needed. The proposal will reach the proposed state only if all of the open issues are classified and properly handled.

Changing of the modules of MapServer should be in accordance of the primary maintainer. The maintainer may reserve the right to commit the changes proposed by this RFC during the implementation phase. Otherwise the primary author Tamas Szekeres will be responsible to make the changes.

The problems of the current implementation and the proposed changes will be enumerated later. To handle these issues the following options will be considered ordering by the expediency. The latter is the less expedient.

2.1 Not changing the code (Considering as safe without locks)

Some of the process-wide global variables store invariant data - like enumerations - having been initialized at compilation time. Since these variables can be simultaneously read by multiple threads there is nothing to be done and even the locks should be removed - if exist - ensuring the thread isolation to be realized. Might be redeclared as "static const".

2.2 Retaining the variable, but reconsidering the initialization code

Some of the global variables store invariant data but the initial value is assigned at run time during the module initialization phase. In this situation we should ensure that the initialization will be done before the subsequent access is receivable to prevent from the possibility of race conditions. msSetup() is designated as a suitable place to make module level initialization of the variables. Mapscript language bindings are encouraged to provide calling msSetup automatically at module startup. The current locking strategy should be reviewed and the locks should be restricted to the initialization code.

2.3 Rewriting the code to eliminate the need of the global variable

If the global variable could be eliminated by changing the code structure we will consider to make these changes. The locks related to these variables should be removed.

2.4 Using thread local variable instead of the global one

When the usage of the global variable cannot be eliminated easily we will consider to use thread local variables instead. To implement thread local variables, theoretically we have at least 2 options to do:

  1. By modifying the variable declaration we could use storage specifiers like Microsoft specific "__declspec(thread)" or "__thread" on the POSIX environments.

  2. By implementing the allocation of the thread local memory utilizing the platform dependent TLS APIs like the Windows TLS or the unix Pthreads implementation.

The first one is not applicable for MapServer since the Microsoft implementation does not allow delay loading of the dll containing variables declared with __declspec(thread).

The second approach is applicable, however rewriting the existing code may be more difficult. GDAL has a sample implementation for it might be taken over.

2.5 Not changing the code (Marking as safe with locks, will be reconsidered later)

Some of the MapServer code and/or the related external libraries may not be modified easily and will be protected by locks in this phase of the realization. These issues will be kept open and the solution for the thread isolation might be reconsidered later. The open issues will be enumerated and documented along with the MapServer source files and the thread safety FAQ

http://mapserver.gis.umn.edu/docs/faq/thread_safety

2.6 Not changing the code (Marking as unsafe, will be deprecated and unsupported)

Having lack of support of portions of the existing code may keep from changing the code easily. These portions of the code will not be modified in this phase of the realization. These issues will be kept open and might be reconsidered later. The open issues will be enumerated and documented along with the MapServer source files and the thread safety FAQ.

3. Issues of the MapServer /Mapscript code

This chapter will enumerate the sections of the existing code should be reconsidered according to the options mentioned previously. The line numbers may slightly change according to the developers work.

epplib.c(47):static int REVERSE; /* set to 1 on bigendian machines */

This variable is set in eppreset using the following code { union { long i; char c[4]; } u; u.i=1; REVERSE=(u.c[0]==0); } should be set during the initialization or by the makefile as a predefined constant. Also the MapServer configure script may include byte order detection. This constant might be used by other modules.

SDL: This constant is limited to epplib.c...

mapcpl.c(57):static char     szStaticResult[MS_PATH_BUF_SIZE];

FrankW: In fact this code carried over from GDAL was later remodelled in GDAL. It looks like msGetBasename() is only used in a few places and we should remodel those to avoid using a static buffer.

maperror.c(110):static char *ms_errorCodes[MS_NUMERRORCODES] = {"",
maperror.c(154):    static errorObj ms_error = {MS_NOERR, "", "", NULL};
maperror.c(169):static te_info_t *error_list = NULL;
maperror.c(552):  static char version[1024];
maperror.c(651):    static char nonblocking_set = 0;

TODO

mapfile.c(184):static char *msUnits[8]={"INCHES", "FEET", "MILES", "METERS", "KILOMETERS", "DD",  "PIXELS", "PERCENTAGES"};
mapfile.c(185):static char *msLayerTypes[8]={"POINT", "LINE", "POLYGON", "RASTER", "ANNOTATION", "QUERY", "CIRCLE", "TILEINDEX"};
mapfile.c(186):char *msPositionsText[MS_POSITIONS_LENGTH] = {"UL", "LR", "UR", "LL", "CR", "CL", "UC", "LC", "CC", "AUTO", "XY", "FOLLOW"}; /* msLabelPositions[] also used in mapsymbols.c (not static) */
mapfile.c(187):static char *msBitmapFontSizes[5]={"TINY", "SMALL", "MEDIUM", "LARGE", "GIANT"};
mapfile.c(188):static char *msQueryMapStyles[4]={"NORMAL", "HILITE", "SELECTED", "INVERTED"};
mapfile.c(189):static char *msStatus[5]={"OFF", "ON", "DEFAULT", "EMBED"};
mapfile.c(191):static char *msTrueFalse[2]={"FALSE", "TRUE"};
mapfile.c(193):static char *msJoinType[2]={"ONE-TO-ONE", "ONE-TO-MANY"};

The above are all const static data, being handled as 2.1.

mapgd.c(239):static unsigned char PNGsig[8] = {137, 80, 78, 71, 13, 10, 26, 10}; /* 89 50 4E 47 0D 0A 1A 0A hex */
mapgd.c(240):static unsigned char JPEGsig[3] = {255, 216, 255}; /* FF D8 FF hex */
mapgd.c(911):  static gdPoint points[38];
mapgd.c(2428):  static double last_style_size;
mapgd.c(2719):  static int styleIndex, styleVis;
mapgd.c(2720):  static double styleSize=0, styleCoef=0, last_style_size=-1;
mapgd.c(2721):  static int last_style_c=-1, last_style_stylelength=-1, last_styleVis=0;

TODO

mapgdal.c(141):static int    bGDALInitialized = 0;

Will be handled as 2.2. (Startup initialization)

mapgeos.cpp(98):GeometryFactory *gf=NULL;

Will be handled as 2.2. (Startup initialization)

maphttp.c(140):static int gbCurlInitialized = MS_FALSE;

Will be handled as 2.2. (Startup initialization)

mapimagemap.c(141):static char *layerlist=NULL;
mapimagemap.c(142):static int layersize=0;
mapimagemap.c(143):static pString imgStr, layerStr={ &layerlist, &layersize, 0 };
mapimagemap.c(146):static const char *polyHrefFmt, *polyMOverFmt, *polyMOutFmt;
mapimagemap.c(147):static const char *symbolHrefFmt, *symbolMOverFmt, *symbolMOutFmt;
mapimagemap.c(148):static const char *mapName;
mapimagemap.c(150):static int suppressEmpty=0;
mapimagemap.c(229):static int lastcolor=-1;
mapimagemap.c(273):static char* lname;
mapimagemap.c(274):static int dxf;

Imagemap support is not widely used, will be handled as 2.6 for now. Still waiting for comments.

mapio.c(67):static int is_msIO_initialized = MS_FALSE;
mapio.c(69):static msIOContext default_stdin_context;
mapio.c(70):static msIOContext default_stdout_context;
mapio.c(71):static msIOContext default_stderr_context;
mapio.c(73):static msIOContext current_stdin_context;
mapio.c(74):static msIOContext current_stdout_context;
mapio.c(75):static msIOContext current_stderr_context;

FrankW: Currently there is only one process wide set of "io handlers" for io. This will almost certainly need to change at some point to be thread local in some fashion. I hope to address this when I work on the redirectable OWS services accessible from MapScript this spring.

maplexer.c(220):static YY_BUFFER_STATE yy_current_buffer = 0;
maplexer.c(230):static char yy_hold_char;
maplexer.c(232):static int yy_n_chars;         /* number of characters read into yy_ch_buf */
maplexer.c(238):static char *yy_c_buf_p = (char *) 0;
maplexer.c(239):static int yy_init = 1;                /* whether we need to initialize */
maplexer.c(240):static int yy_start = 0;       /* start state number */
maplexer.c(245):static int yy_did_buffer_switch_on_eof;
maplexer.c(306):static yyconst short int yy_accept[2442] =
maplexer.c(579):static yyconst int yy_ec[256] =
maplexer.c(611):static yyconst int yy_meta[52] =
maplexer.c(621):static yyconst short int yy_base[2456] =
maplexer.c(895):static yyconst short int yy_def[2456] =
maplexer.c(1169):static yyconst short int yy_nxt[2776] =
maplexer.c(1478):static yyconst short int yy_chk[2776] =
maplexer.c(1787):static yy_state_type yy_last_accepting_state;
maplexer.c(1788):static char *yy_last_accepting_cpos;
maplexer.c(1886):static int yy_start_stack_ptr = 0;
maplexer.c(1887):static int yy_start_stack_depth = 0;
maplexer.c(1888):static int *yy_start_stack = 0;

According to Steve's suggestions we will address this with newer versions flex and bison invoked appropriately.

mapmygis.c(245):static int gBYTE_ORDER = 0;

TODO

mapogcsos.c(1974):    static char *request=NULL, *service=NULL;

TODO

mapogr.cpp(840):static int bOGRDriversRegistered = MS_FALSE;

Will be handled as 2.2. (Startup initialization)

mapows.c(1647):  static char epsgCode[20] ="";

TODO

mapparser.c(282):static const unsigned char yytranslate[] =
mapparser.c(317):static const unsigned char yyprhs[] =
mapparser.c(328):static const yysigned_char yyrhs[] =
mapparser.c(354):static const unsigned short yyrline[] =
mapparser.c(368):static const char *const yytname[] =
mapparser.c(381):static const unsigned short yytoknum[] =
mapparser.c(391):static const unsigned char yyr1[] =
mapparser.c(402):static const unsigned char yyr2[] =
mapparser.c(415):static const unsigned char yydefact[] =
mapparser.c(431):static const yysigned_char yydefgoto[] =
mapparser.c(439):static const short yypact[] =
mapparser.c(455):static const yysigned_char yypgoto[] =
mapparser.c(465):static const unsigned char yytable[] =
mapparser.c(491):static const yysigned_char yycheck[] =
mapparser.c(519):static const unsigned char yystos[] =

According to Steve's suggestions we will address this with newer versions flex and bison invoked appropriately.

mappluginlayer.c(46):static VTFactoryObj gVirtualTableFactory = {MS_MAXLAYERS, 0, {NULL}};

TODO

mappool.c(206):static int connectionCount = 0;
mappool.c(207):static int connectionMax = 0;
mappool.c(208):static connectionObj *connections = NULL;

TODO

mapproject.c(889):static char *ms_proj_lib = NULL;
mapproject.c(890):static char *last_filename = NULL;
mapproject.c(918):    static int finder_installed = 0;

TODO

mapscale.c(68):static char *unitText[5]={"in", "ft", "mi", "m", "km"};
mapscale.c(69):double inchesPerUnit[6]={1, 12, 63360.0, 39.3701, 39370.1, 4374754};

const static data, being handled as 2.1.

mapsde.c(216):static int lcacheCount = 0;
mapsde.c(217):static int lcacheMax = 0;
mapsde.c(218):static layerId *lcache = NULL;

TODO

mapserv.c(1196):        static int nRequestCounter = 1;

Considering safe without locks (2.1). The mapserv application does not involved in multiple threads.

mapshape.c(70):static int      bBigEndian;

Should be handled as epplib.c(47):static int REVERSE;

mapsvg.c(164):    static char epsgCode[20] ="";
mapsvg.c(165):    static char *value;

TODO

mapswf.c(97):static char gszFilename[128];
mapswf.c(98):static char gszAction[256];
mapswf.c(99):static char gszTmp[256];

TODO

mapsymbol.c(154):static char *msCapsJoinsCorners[7]={"NONE", "BEVEL", "BUTT", "MITER", "ROUND", "SQUARE", "TRIANGLE"};

const static data, being handled as 2.1.

mapthread.c(196):static int thread_debug = 0;
mapthread.c(198):static char *lock_names[] =

const static data, being handled as 2.1.

mapthread.c(213):static int mutexes_initialized = 0;
mapthread.c(214):static pthread_mutex_t mutex_locks[TLOCK_MAX];
mapthread.c(223):    static pthread_mutex_t core_lock = PTHREAD_MUTEX_INITIALIZER;
mapthread.c(294):static int mutexes_initialized = 0;
mapthread.c(295):static HANDLE mutex_locks[TLOCK_MAX];
mapthread.c(305):    static HANDLE core_lock = NULL;

Should be handled as 2.2. (Startup initialization). Initialized in msThreadInit();

maputil.c(1068):static int tmpCount = 0;
maputil.c(1069):static char *ForcedTmpBase = NULL;

TODO

mapwms.c(252):static char *wms_exception_format=NULL;

TODO

md5c.c(58):static unsigned char PADDING[64] = {

const static data, being handled as 2.1.

strptime.c(43):static const char *abb_weekdays[] = {
strptime.c(54):static const char *full_weekdays[] = {
strptime.c(65):static const char *abb_month[] = {
strptime.c(81):static const char *full_month[] = {
strptime.c(97):static const char *ampm[] = {

const static data, being handled as 2.1.

gdft\gdkanji.c(103):   static int whatcode;
gdft\gdkanji.c(403):   static unsigned char tmp[BUFSIZ];
gdft\gdkanji.c(489):   static unsigned char tmp_dest[BUFSIZ];

SDL: gdft and it's contents are not part of MapServer distributions.

gdft\gdttf.c(712):  static gdCache_head_t *tweenColorCache=NULL; /****** set up tweenColorCache on first call ************/
gdft\gdttf.c(852):  static gdCache_head_t      *fontCache=NULL; /****** initialize font engine on first call ************/
gdft\gdttf.c(853):  static TT_Engine   engine;

SDL: gdft and it's contents are not part of MapServer distributions.

gdft\jisx0208.h(7):static unsigned short UnicodeTbl[][94] = {

SDL: gdft and it's contents are not part of MapServer distributions.

mapscript\csharp\csmodule.i(32):               static $moduleHelper()
mapscript\csharp\csmodule.i(41):       static $moduleHelper the$moduleHelper = new $moduleHelper();

Initialization startup code.

mapscript\php3\mapscript_i.c(223):    static int i=0;
mapscript\php3\mapscript_i.c(1137):        static char pszFieldName[1000];

TODO

mapscript\php3\php_mapscript.c(789):static int le_msmap;
mapscript\php3\php_mapscript.c(790):static int le_mslayer;
mapscript\php3\php_mapscript.c(791):static int le_msimg;
mapscript\php3\php_mapscript.c(792):static int le_msclass;
mapscript\php3\php_mapscript.c(793):static int le_mslabel;
mapscript\php3\php_mapscript.c(794):static int le_mscolor;
mapscript\php3\php_mapscript.c(795):static int le_msrect_new;
mapscript\php3\php_mapscript.c(796):static int le_msrect_ref;
mapscript\php3\php_mapscript.c(797):static int le_mspoint_new;
mapscript\php3\php_mapscript.c(798):static int le_mspoint_ref;
mapscript\php3\php_mapscript.c(799):static int le_msline_new;
mapscript\php3\php_mapscript.c(800):static int le_msline_ref;
mapscript\php3\php_mapscript.c(801):static int le_msshape_new;
mapscript\php3\php_mapscript.c(802):static int le_msshape_ref;
mapscript\php3\php_mapscript.c(803):static int le_msshapefile;
mapscript\php3\php_mapscript.c(804):static int le_msweb;
mapscript\php3\php_mapscript.c(805):static int le_msrefmap;
mapscript\php3\php_mapscript.c(806):static int le_msprojection_new;
mapscript\php3\php_mapscript.c(807):static int le_msprojection_ref;
mapscript\php3\php_mapscript.c(808):static int le_msscalebar;
mapscript\php3\php_mapscript.c(809):static int le_mslegend;
mapscript\php3\php_mapscript.c(810):static int le_msstyle;
mapscript\php3\php_mapscript.c(811):static int le_msoutputformat;
mapscript\php3\php_mapscript.c(812):static int le_msgrid;
mapscript\php3\php_mapscript.c(813):static int le_mserror_ref;
mapscript\php3\php_mapscript.c(814):static int le_mslabelcache;
mapscript\php3\php_mapscript.c(815):static int le_mssymbol;
mapscript\php3\php_mapscript.c(816):static int le_msquerymap;
mapscript\php3\php_mapscript.c(857):static zend_class_entry *map_class_entry_ptr;
mapscript\php3\php_mapscript.c(858):static zend_class_entry *img_class_entry_ptr;
mapscript\php3\php_mapscript.c(859):static zend_class_entry *rect_class_entry_ptr;
mapscript\php3\php_mapscript.c(860):static zend_class_entry *color_class_entry_ptr;
mapscript\php3\php_mapscript.c(861):static zend_class_entry *web_class_entry_ptr;
mapscript\php3\php_mapscript.c(862):static zend_class_entry *reference_class_entry_ptr;
mapscript\php3\php_mapscript.c(863):static zend_class_entry *layer_class_entry_ptr;
mapscript\php3\php_mapscript.c(864):static zend_class_entry *label_class_entry_ptr;
mapscript\php3\php_mapscript.c(865):static zend_class_entry *class_class_entry_ptr;
mapscript\php3\php_mapscript.c(866):static zend_class_entry *point_class_entry_ptr;
mapscript\php3\php_mapscript.c(867):static zend_class_entry *line_class_entry_ptr;
mapscript\php3\php_mapscript.c(868):static zend_class_entry *shape_class_entry_ptr;
mapscript\php3\php_mapscript.c(869):static zend_class_entry *shapefile_class_entry_ptr;
mapscript\php3\php_mapscript.c(870):static zend_class_entry *projection_class_entry_ptr;
mapscript\php3\php_mapscript.c(871):static zend_class_entry *scalebar_class_entry_ptr;
mapscript\php3\php_mapscript.c(872):static zend_class_entry *legend_class_entry_ptr;
mapscript\php3\php_mapscript.c(873):static zend_class_entry *style_class_entry_ptr;
mapscript\php3\php_mapscript.c(874):static zend_class_entry *outputformat_class_entry_ptr;
mapscript\php3\php_mapscript.c(875):static zend_class_entry *grid_class_entry_ptr;
mapscript\php3\php_mapscript.c(876):static zend_class_entry *error_class_entry_ptr;
mapscript\php3\php_mapscript.c(877):static zend_class_entry *labelcache_class_entry_ptr;
mapscript\php3\php_mapscript.c(878):static zend_class_entry *symbol_class_entry_ptr;
mapscript\php3\php_mapscript.c(879):static zend_class_entry *querymap_class_entry_ptr;
mapscript\php3\php_mapscript.c(891):static unsigned char one_arg_force_ref[] =
mapscript\php3\php_mapscript.c(893):static unsigned char two_args_first_arg_force_ref[] =

TODO

mapscript\php3\php_proj.c(196):static zend_class_entry *proj_class_entry_ptr;
mapscript\php3\php_proj.c(200):static int le_projobj;
mapscript\php3\php_proj.c(294):static long _php_proj_build_proj_object(PJ *pj,

TODO

mapscript\swiginc\dbfinfo.i(40):        static char pszFieldName[1000];

TODO

mapscript\swiginc\map.i(204):    static int i=0;

TODO

4. Considerations for the future development

Developers should keep in mind that their code may be called by multiple threads simultaneously. Using process-wide global variables modified at run time will be discouraged in the future. If the usage is inevitable the variables should be protected by using mutual exclusion properly depending on the USE_THREAD constant.

5. Backwards compatibility issues

This is binary and source code level backward incompatible change. The compatibility of the mapscript interface might be kept.

Bug ID

The following bug is added as a primary source for collecting the developer

and user response. Nevertheless the MapServer users and developers list will also be monitored and the RFC will be updated accordingly by the primary author.

Bug 1764

Voting history

Still not proposed for voting