MS RFC 24:MapScript内存管理¶
- 日期
2006/12/3
- 作者
乌姆贝托·尼科莱蒂
- 联系
- 最后编辑
2006/12/31
- 状态
多恩
- 版本
MAPServer 5
- 跟踪器
- 跟踪器3.2
- 身份证件
$ID$
概述¶
swig包装机中的内存管理具有困难和容易出错的传统。包装器的程序员必须处理可分配的内存,然后在两个独立的环境中释放:宿主语言,如Java、C语言或Perl和封装的本机代码。
大多数现代语言都实现了垃圾收集,因此开发人员不必关心内存管理。编程语言跟踪内存(或对象,实际上是)分配,当对象超出范围时(不能再从运行的程序访问),它将其标记为符合垃圾回收条件。后台进程偶尔会唤醒并释放与标记对象相关联的内存。有关GC的详细信息,请参阅维基百科词条:https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)
在大多数情况下发生的是,一些内存被分配在Java中,然后通过调用一些被包装的方法指向另一个指针。最终,GC运行并释放内存。一旦其他指针被取消引用,宿主语言就会因为分段错误而崩溃(在UNIX术语中)。
MapServer Swig包装器遇到垃圾收集问题,例如在动态地向地图添加层时。
此RFC的目的是解决这些问题,并提供一个可在MapServer 5.0发布时及时实现的解决方案。
此RFC不涉及线程安全。
问题描述¶
本节概述了mapscript内存管理中的错误(连同示例)。大多数例子都是用Java编写的,但是它们也适用于所有其他的脚本。截至2006年12月31日,它们可以根据mapserver head的最新cvs代码进行复制。
对象相等/标识¶
考虑下面的JavaMaScript代码:
mapObj map=new mapObj("my.map");
layerObj layer=new layerObj(null);
// add layer to the map
int index=map.insertLayer(layer, -1);
// set its name
layer.setName("Change me");
// fetch from map
layerObj newLayer=map.getLayer(index);
// they should be the same...
System.out.println(newLayer.getName()+"=="+layer.getName());
// and this should print true (it is a reference comparison)
System.out.println(newLayer==layer);
执行时将产生以下输出:
null==Change me
false
这是因为当前的实现策略在将层插入到映射中时复制该层。Java引用没有重新指向新副本,因此与实际内存区域“断开”。这种情况目前发生在最常用的插入方法(即 插入类) .
早期垃圾收集¶
通过mapscript创建的对象可以“早期”被垃圾收集,当有活动对象仍在引用它们时。在Java中看到这个例子:
mapObj map=new mapObj("data/emptymap.map");
layerObj layer=new layerObj(map);
layer.setName("Layer 0");
classObj clazz=new classObj(null);
clazz.setName("Clazz 0 NULL");
int pos=layer.insertClass(clazz, -1);
map=null;
layer=null;
// force garbage collection
for(int i=0;i<100;i++)
System.gc();
clazz.getLayer().getMap().draw();
// Java crashes because memory has been freed!
及其Perl等价物:
use mapscript;
$map = new mapscript::mapObj("../../tests/test.map");
$layer = new mapscript::layerObj($map);
print "Before first draw for $layer->{map}\n";
$layer->{map}->draw();
print "Map drawn, now undef map\n";
$map = undef;
$map1=$layer->{map}->draw();
// perl interpreter segfault
动态填充的层、类等¶
请参阅以下错误报告:
https://github.com/MapServer/MapServer/issues/1400
https://github.com/MapServer/MapServer/issues/1743
https://github.com/MapServer/MapServer/issues/1841
请注意,这一问题可能很难复制,信贷去塔玛斯指出它。
拟议实施¶
为解决第2.1、2.2和2.3项所示的问题,本招标文件提出:
引用计数器被添加到每个mapserver数据结构中,这些数据结构可以直接用mapscript操作,并插入/添加到另一个对象(即layerobj)中。
每当mapscript创建对mapserver数据结构的引用时,setter、getter和insert方法都会增加计数器的值。
所有mapscript对象始终归swig所有,或者更确切地说,归wrapper对象所有(*swigcmown*始终为true)
修改mapserver free*方法,以便只有当计数器为零时才释放底层数据结构,否则减少它。
对包装器对象进行扩充,以仅维护对其父对象的引用,并防止早期垃圾收集
所有结构数组(map->layers等)都将更改为指针数组,以消除对insert方法执行复制操作的需要。
通过初步讨论,决定放弃完全支持对象平等的要求。因此,将实施2.1,以便 only 第一个比较返回true。
此RFC应适用于所有mapscript语言(经过必要的修改)。Perl或Java的例子是因为作者熟悉这些语言。
以下小节将更详细地描述上述各项。第3.4和3.5小节为layerObj类提供了一个实现示例。请注意,在以下内容中,我们将分析的范围限制为类/层关系。
实现引用计数器¶
实现此RFC的mapscript对象将得到一个新的 int 成员称为 再计数.
mapscript将保持对引用计数器的只读访问,这对调试很有用
参考计数器的递增和递减将由以下宏实现:
#define MS_REF_INCR(obj) obj->refcount++
#define MS_REF_DECR(obj) (--(obj->refcount))
另一种方法是在全局哈希图中保持引用计数,该哈希图由内存地址键控。这将消除对 every 对象,但可能对性能产生更大的影响。尤其是哈希函数必须仔细选择。
http://en.wikipedia.org/wiki/Hash_table
跟踪器上提出的示例实现 bug #2032 采用第一种策略。
3.1.1 CGI中的重新计数¶
应修改RFC,以建议将使用mapscript要求删除在以下动机上:
随着使用mapscript的出现,如果用户需要构建mapscript而不是CGI,那么编译过程将不同。虽然这对个人来说不算什么,但可能会给那些维护诸如ms4w或fwtools之类的二进制发行版带来额外的负担。
启用refcounting不会损害CGI,因为额外的if和++
以后我们可以选择引入use_mapscript
它简化了构建过程,保持到目前为止的状态
它简化了开发人员的生活,因为少了一个定义来维护
以下是记录使用“MapScript”的文本(现在已过时)。
由于CGI不会使用此成员,因此,RFC建议:
它由一个新的define use_mapscript包装
一个新的 configure 添加了名为*--启用mapscript的选项*
从5.0开始,需要指定*--启用mapscript*来构建任何mapscript(可能除了php)
例子:
/* CLASS OBJECT - basic symbolization and classification information */
typedef struct class_obj{
#ifndef USE_MAPSCRIPT
#ifdef SWIG
%immutable;
#endif /* SWIG */
int refcount;
#ifdef SWIG
%mutable;
#endif /* SWIG */
#endif
expressionObj expression; /* the expression to be matched */
#endif
选票计数:
+1:Umberto、Tamas和Daniel
添加对mapscript包装对象的引用¶
将修改mapscript对象,使其保持对添加到其中的其他mapscript对象的引用,就像C结构已经做的那样。此对象在下文中称为 父对象.
例如,对于layerObj,layerObj类将被扩展为包含
对包含层的mapobj的引用
这些更改的目的是宿主语言了解这些对象之间的关系,并解决早期垃圾收集问题。当层取消引用其父对象(grep'layer->map'.c*报告105种用法)时,这对于避免宿主语言中的意外崩溃也很重要。
如前所述,已决定放弃完全支持对象平等/标识的要求。
这个项目将在第二个阶段中实现,在基本的refcounting到位之后。此外,RFC建议对使用父映射引用的层操作执行父级非空检查。
更改指针数组中的结构数组¶
这一变化将发生在C级,是一个相当大的任务。最初,为了这个RFC的目的,数组的大小仍然是固定的,就像现在一样。对代码的修改将成为未来的RFC寻址动态大小数组的基础。
策略如下:
对数组元素的所有访问都可以用一个方便的C宏进行包装。这样,实现就从客户机代码中抽象出来了。
将修改结构定义和free/init方法以实现新功能
将修改宏以适应上一项的实现
要实现项目1,我们将使用如下Perl饼图:
perl -pi -e "s/([mM])ap->layers\[(.*?)\]\]\./GET_LAYER(\1ap, \2\])->/g" *.c
perl -pi -e "s/([mM])ap->layers\[(.*?)\]\./GET_LAYER(\1ap, \2)->/g" *.c
perl -pi -e "s/([mM])ap->layers\[(.*?)\]\]/GET_LAYER(\1ap, \2\])/g" *.c
perl -pi -e "s/([mM])ap->layers\[(.*?)\]/GET_LAYER(\1ap, \2)/g" *.c
perl -pi -e "s/dst->layers\[(.*?)\]\./GET_LAYER(dst, \1)->/g" *.c
perl -pi -e "s/src->layers\[(.*?)\]\./GET_LAYER(src, \1)->/g" *.c
perl -pi -e "s/dst->layers\[(.*?)\]/GET_LAYER(dst, \1)/g" *.c
perl -pi -e "s/src->layers\[(.*?)\]/GET_LAYER(src, \1)/g" *.c
这是将要使用的宏:
#define GET_LAYER(map, pos) map->layers[pos]
这将只留下很少的事件(约4或5),必须手工编辑。同样的方法也将用于其他结构数组(类和样式)。
此项已在不使用get_class宏的情况下为类实现。
保持父引用和C内部结构同步¶
为了使上一项中描述的机制能够工作,必须修改mapscript对象和本机代码中的某些函数,以便mapscript对象和C数据结构保持同步。
回到layerobj的例子 插入类, getClass 和 removeClass 必须修改方法以使父引用与C数据结构保持同步。当前方法将通过使用特定的类型映射进行修改。还需要修改构造函数来存储对mapobj的引用。最后,实际执行复制和插入操作的本机代码必须修改为只执行插入操作(layerObject.c,第52行,函数 MnSerSert类) .
mapscript API将向后兼容。
析构函数服从参考计数器¶
在释放内存之前,各种free*方法必须检查计数器。这将在本机代码中实现,如下例所示:
void msFreeMap(mapObj *map) {
if(!map) return;
if ( MS_REF_DECR(obj) > 0) return;
// go on destroying the object as usual
}
这将确保在父级之前被垃圾收集的情况下不会释放子级。为了避免家长试图双倍释放部分子女:
如果父级在其子级之前被销毁,则父级应使子级中的指针无效。
Viceversa,当孩子比他们的父母更早被接收时,他们必须使他们在父母中的指针无效(我们正在评估一旦父母引用到位是否会发生这种情况)
始终将对象所有权授予Swig¶
要使引用计数起作用,所有对象所有权都必须授予swig。这和今天的情况大不相同。但是,更改很简单,因为Swig在默认情况下会获得对象所有权,并且只需要删除Swig接口文件中的所有*%newObject*语句。
目前有58个*%newObject*语句。
C还必须在 CSMask.:
csharp/csmodule.i:375: if (map != null) this.swigCMemOwn = false;$excode
csharp/csmodule.i:379: if (layer != null) this.swigCMemOwn = false;$excode
csharp/csmodule.i:383: if (parent_class != null) this.swigCMemOwn = false;$excode
或者完全删除构造函数自定义。
Java:LabelObjo的MpScript代码示例¶
Tamas提出了一种更面向对象的方法来解决这个问题,它可以被那些支持面向对象的语言所采用。
layerobj(javamodule.i)的代码示例:
/*
Modified constructor according to:
- cache population and sync, item 3.2
*/
%typemap(javaconstruct) layerObj(mapObj map) %{ {
this($imcall, true);
if (map != null) {
/* Store parent reference, item 3.2 */
this.map=map;
}
}
%}
%typemap(javaout) int insertClass {
// call the C API, which needs to be modified
// so that the classObj is not copied anymore
int actualIndex=$jnicall;
/* Store parent reference, item 3.2 */
classobj.layer=this;
return actualIndex;
}
%typemap(javacode) layerObj %{
/* parent reference, item 3.2 */
mapObj map=null;
%}
%typemap(javacode) classObj %{
/* parent reference, item 3.2 */
layerObj layer=null;
%}
用于layerobj的perl:mapscript代码示例¶
layerObj(plmodule.i)的代码示例:::
%feature("shadow") layerObj(mapObj *map)
%{
sub new {
my $pkg = shift;
my $self = mapscriptc::new_layerObj(@_);
bless $self, $pkg if defined($self);
if (defined($_[0])) {
# parent reference
mapscript::LAYER_ADD_MAP_REF($self, $_[0]);
}
return $self;
}
%}
%feature("shadow") ~layerObj()
%{
sub DESTROY {
return unless $_[0]->isa('HASH');
my $self = tied(%{$_[0]});
return unless defined $self;
delete $ITERATORS{$self};
mapscriptc::delete_layerObj($self);
# remove parent reference
delete $mapscript::LAYERMAP{$self};
}
%}
%perlcode %{
%LAYERMAP={};
sub LAYER_ADD_MAP_REF {
my ($layer, $map)=@_;
#print "MAP key=" . tied(%$layer) . "\n";
$LAYERMAP{ tied(%$layer) }=$map;
}
##################
# DEBUGGING ONLY #
##################
sub getLayerFrom {
my ($map, $idx)=@_;
return $MAPLAYERS{tied(%$map)}->{$idx};
}
sub getMAPLAYERS {
return \%MAPLAYERS;
}
%}
实施计划¶
对于大多数MaScript(Java、CpHARP、Perl和Python),在SWIG中有足够的功能来实现这个RFC中描述的特性。对于Ruby,我们可能需要走一条不同的路线,实现*%trackobjects*特性来实现3.2。至于TCL,我目前不知道是否有可能。
下面的两个部分详细描述了所需的swig-mapscript特性(代码注入和构造函数定制)。每种语言都会得到一个特定的部分来处理其自身的特性
正在检查swig mapscript功能:%javacode¶
swig为以下语言提供了相当于*%javacode*:
Perl到%PerlCode
python通过%pythoncode
CSharp通过%CScode
Ruby的包装对象中根本没有任何Ruby代码
swig tcl不支持%tclcode构造
这个swig构造将用于在包装器中注入3.2中描述的引用的定义和包装器方法。
检查swig mapscript功能:构造函数自定义¶
用于包装和自定义MaScript对象的结构体(项目3.3)的%CSCuto只能在CSHARP和Java中使用。应该可以在python中使用*%pythonprepend*或*%pythonprepend*模拟其行为,在perl中使用*%perlcode*或*%feature(“shadow”)*模拟其行为。
此swig构造将用于填充父级backreference。
Java与C锐¶
SWIG Java和SWIG CSARP共享一个共同的基础,因此非常相似。SWIG Java构造的名称可以通过改变 java 前缀为 cs (即 javacode 在里面 CSC码, javaconstruct 在里面 csconstruct 和 javaout 在里面 出库) .
实施应完全遵循本RCF,或基于Tamas在最后一个讨论主题中提出的建议。
备注
截至2008年1月,Tamas决定自己编写 MS RFC 24:MapScript内存管理 对C
珀尔¶
在上面的示例中,大多数Perl定制都可以使用 shadow 构造。
实现将完全遵循这个RFC。
Python¶
python在swig中享有一级支持,因此RFC应该按照描述的方式实现。
红宝石¶
需要调查,我们可能需要使用rb_gc_ux函数来标记对象并防止其垃圾收集,或者使用*%trackobjects*。从3.2项开始,Ruby将不实现这个RFC。
Tcl¶
需要调查和TCL专家。在编写本文时,TCL可能不会在第3.2项中实现此RFC。
PHP¶
php-mapscript不依赖swig,但是由于大多数代码是本机代码,所以应该可以采用这个rfc。
实施检查表¶
下表将用于跟踪此RFC的实现状态。每个mapscript对象都有一个表,当一种语言为一个给定的对象实现了这个rfc时,维护人员将用以下标记之一填充相对单元格:
<if the child->parent reference has been implemented
如果对象的引用计数器已增加(在cnt=counter列下),则为加号(+)
如果对象的引用计数器已减少(在cnt=counter列下),则为减号(-)
=1,如果参考计数器已重置为1(在cnt=counter列下)
MAPbOBJ¶
方法 |
CNT |
爪哇 |
C# |
珀尔 |
Python |
tcl |
ruby |
---|---|---|---|---|---|---|---|
mapObj(构造器) |
+ |
||||||
GET符号集 |
|||||||
获取字体集 |
|||||||
图形标签缓存 |
|||||||
获得范围 |
|||||||
setSaved_extent |
|||||||
getSaved_extent |
|||||||
地球生物色 |
|||||||
获取输出格式 |
|||||||
获取引用 |
|||||||
获取标尺 |
|||||||
盖特传奇 |
|||||||
获取查询文件 |
|||||||
获取Web |
|||||||
获取配置 |
|||||||
插入层 |
+ |
< |
< |
< |
|||
遥控器 |
- |
||||||
获得层 |
+ |
< |
< |
< |
|||
GETLay-BelyNAME |
+ |
< |
< |
< |
|||
准备图像 |
|||||||
设置输出格式 |
|||||||
画 |
|||||||
图纸查询 |
|||||||
拉锯传奇 |
|||||||
牵引标尺 |
|||||||
图纸参考图 |
|||||||
获取标签 |
|||||||
下标签 |
|||||||
按名称获取输出格式 |
|||||||
追加输出格式 |
|||||||
删除输出格式 |
|||||||
克隆 |
= 1 |
层对象¶
方法 |
CNT |
爪哇 |
C# |
珀尔 |
Python |
tcl |
ruby |
---|---|---|---|---|---|---|---|
layerobj(构造函数) |
+ |
< |
< |
< |
|||
获得地图 |
|||||||
获取网站 |
|||||||
获取元数据 |
|||||||
克隆层 |
= 1 |
||||||
插入类 |
+ |
< |
< |
< |
|||
卸货舱 |
- |
||||||
下一个形状 |
|||||||
获取特征 |
|||||||
获取形状 |
|||||||
获得结果 |
|||||||
获取类 |
+ |
< |
< |
< |
|||
获得结果 |
|||||||
附加特征 |
|||||||
获得范围 |
类对象¶
方法 |
CNT |
爪哇 |
C# |
珀尔 |
Python |
tcl |
ruby |
---|---|---|---|---|---|---|---|
Classobj(构造函数) |
+ |
< |
< |
< |
|||
获取标签 |
|||||||
获取元数据 |
|||||||
获得层 |
|||||||
克隆 |
= 1 |
||||||
创建Legendicon |
|||||||
拖拉图标 |
|||||||
获取样式 |
+ |
||||||
插入样式 |
+ |
||||||
重塑风格 |
- |
韦博伊¶
方法 |
爪哇 |
C# |
珀尔 |
Python |
tcl |
ruby |
---|---|---|---|---|---|---|
Webobj(构造函数) |
||||||
获得地图 |
||||||
获得范围 |
||||||
设置范围 |
||||||
获取元数据 |
样式对象¶
对于StyleObj,当从容器对象中提取它们时,就足以拒绝它们。不需要添加指向容器对象的引用。
方法 |
CNT |
爪哇 |
C# |
珀尔 |
Python |
tcl |
ruby |
---|---|---|---|---|---|---|---|
StyleObj(构造函数) |
+ |
||||||
设置颜色 |
|||||||
获取颜色 |
|||||||
获取背景颜色 |
|||||||
后退背景颜色 |
|||||||
获取输出颜色 |
|||||||
设置输出颜色 |
|||||||
获得颜色 |
|||||||
设置最小颜色 |
|||||||
获得最大颜色 |
|||||||
设置最大颜色 |
|||||||
克隆 |
= 1 |
拉贝洛夫¶
对于labelobjs来说,当它们从容器对象中提取时,就足以拒绝它们。不需要添加指向容器对象的引用。
方法 |
爪哇 |
C# |
珀尔 |
Python |
tcl |
ruby |
---|---|---|---|---|---|---|
Labelobj(构造函数) |
||||||
获取颜色 |
||||||
设置颜色 |
||||||
设置输出颜色 |
||||||
获取输出颜色 |
||||||
设置阴影颜色 |
||||||
获得阴影颜色 |
||||||
获取背景颜色 |
||||||
后退背景颜色 |
||||||
后退地面阴影颜色 |
||||||
获取背景阴影颜色 |
哈希表对象¶
对于hashtableobj,当从容器对象(即layerobj)中提取它们时,就足以使它们脱离。不需要添加指向容器对象的引用。
方法 |
爪哇 |
C# |
珀尔 |
Python |
tcl |
ruby |
---|---|---|---|---|---|---|
hashTableObj(构造函数) |
色度Objo¶
对于colorobj,当从容器对象中提取它们时,就足以使它们不受支持。不需要添加指向容器对象的引用。
方法 |
爪哇 |
C# |
珀尔 |
Python |
tcl |
ruby |
---|---|---|---|---|---|---|
colorobj(构造函数) |
图像对象¶
对于ImageObjs,当从容器对象中提取它们时,足够拥有它们。不需要添加指向容器对象的引用。
方法 |
爪哇 |
C# |
珀尔 |
Python |
tcl |
ruby |
---|---|---|---|---|---|---|
ImageObj(构造函数) |
形状对象¶
对于shapeobjs,当它们从容器对象中提取或添加时,足够正确地设置所有权。不需要添加指向容器对象的引用。
方法 |
爪哇 |
C# |
珀尔 |
Python |
tcl |
ruby |
---|---|---|---|---|---|---|
shapeobj(构造函数) |
||||||
盖特林 |
||||||
得界 |
||||||
得到 |
||||||
添加 |
||||||
克隆 |
||||||
复制 |
||||||
缓冲区 |
||||||
凸壳 |
||||||
边界 |
||||||
获得形心 |
||||||
联合 |
||||||
交叉 |
||||||
差异 |
||||||
共鸣 |
线路对象¶
对于LineObjs,当它们从容器对象中提取或添加时,足够正确地设置所有权。不需要添加指向容器对象的引用。
方法 |
爪哇 |
C# |
珀尔 |
Python |
tcl |
ruby |
---|---|---|---|---|---|---|
LineObj(构造函数) |
||||||
获得点 |
||||||
得到 |
||||||
添加 |
||||||
设置 |
彭波托基¶
对于pointobjs,当它们从容器对象中提取或添加时,足够正确地设置所有权。不需要添加指向容器对象的引用。
方法 |
爪哇 |
C# |
珀尔 |
Python |
tcl |
ruby |
---|---|---|---|---|---|---|
PointObj(构造函数) |
||||||
托哈佩 |
符号集对象¶
对于symbolsetobjs,不需要添加指向地图的引用。
方法 |
CNT |
爪哇 |
C# |
珀尔 |
Python |
tcl |
ruby |
---|---|---|---|---|---|---|---|
符号etobj(构造函数) |
|||||||
设置符号(符号obj值) |
|||||||
获取符号 |
+ |
||||||
获取符号(int i) |
+ |
||||||
获得符号符号 |
+ |
||||||
指数 |
|||||||
附录符号 |
+ |
||||||
删除文件 |
- |
符号对象¶
对于Symbolobjs,当它们从容器对象中提取或添加时,就足以正确设置所有权。不需要添加指向容器对象的引用。
方法 |
CNT |
爪哇 |
C# |
珀尔 |
Python |
tcl |
ruby |
---|---|---|---|---|---|---|---|
符号obj(构造函数) |
+ |
||||||
设定点 |
|||||||
获取点 |
|||||||
地理信息 |
|||||||
缝合术 |
公开问题¶
在采用/实施本RFC后,应讨论以下问题。
对象的多个所有者¶
这是添加到多个地图的图层的情况。这应该被禁止,因为层只有一个父引用。在插入时,代码应该检查c父引用是否不为空,在这种情况下引发 errorObj 将由宿主语言在异常情况下转换。
解决方法:用户应该克隆对象,并将克隆添加到第二个映射中。
API兼容性¶
通过在方法签名和用法(即调用顺序、类型、返回代码等)方面保持API向后兼容,保留MapScript用户所做的投资是本RFC的首要任务。
如果该规则有任何例外,则必须证明其合理性,并根据本节进行说明。
状态¶
RFC于2007年1月10日在mapserver-dev上发表了一篇博文,发表了评论。
在对mapserver-dev进行讨论后正在进行修订的RFC。
在MapServer Web上发布了新修订。
2007年4月4日投票结束时通过的RFC:
+1:Umberto、Pericles S.Nacionales、Howard Butler、Stephen Woodbridge
+0:Frank Warmerdam先生
活动¶
这个 bug #2032 将用于跟踪与此RFC相关的活动。
2007年2月20日:附加补丁,用于转换动态分配指针数组中的map->layers(项目3.3)
2007年2月24日:关于MapServer开发的投票提议
2007年4月4日:通过RFC,正在实施中
2008年10月1日:完成RFC实施