MS RFC 24:MapScript内存管理

日期

2006/12/3

作者

乌姆贝托·尼科莱蒂

联系

umberto.nicoletti@gmail.com

最后编辑

2006/12/31

状态

提出

版本

MAPServer 5

0。警告!

重要提示:这是RFC的第一个和过时的版本,保留在这里只是为了参考和有限的时间。

1。概述

swig包装机中的内存管理具有困难和容易出错的传统。包装器的程序员必须处理可分配的内存,然后在两个独立的环境中释放:宿主语言,如Java、C语言或Perl和封装的本机代码。

大多数现代语言实现垃圾收集,这样开发人员就不必关心内存管理了。编程语言跟踪内存(或真正的对象)分配,当对象超出范围(无法从正在运行的程序中访问)时,它将其标记为符合垃圾收集条件。一个后台进程偶尔会唤醒并释放与标记对象相关联的内存。有关gc的详细信息,请参阅此维基百科条目:http://en.wikipedia.org/wiki/garbage_collection_uuu(计算机科学)

在大多数情况下发生的是,一些内存被分配在Java中,然后通过调用一些被包装的方法指向另一个指针。最终,GC运行并释放内存。一旦其他指针被取消引用,宿主语言就会因为分段错误而崩溃(在UNIX术语中)。

MapServer Swig包装器会遇到垃圾收集问题,例如在向地图常规添加层时。

此RFC的目的是解决这些问题,并提供一个可在MapServer 5.0发布时及时实现的解决方案。

此RFC不涉及线程安全。

2。问题描述

本节概述了mapscript内存管理中的错误(连同示例)。大多数例子都是用Java编写的,但是它们也适用于所有其他的脚本。截至2006年12月31日,它们可以根据mapserver head的最新cvs代码进行复制。

2.1对象平等/身份

考虑下面的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引用没有重新指向新副本,因此与实际内存区域“断开”。这种情况目前发生在最常用的插入方法(即 插入类) .

2.2早期垃圾收集

通过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

2.3动态填充的层、类等

请参阅以下错误报告:

http://mapserver.gis.umn.edu/bugs/show_bug.cgi?id=1400

http://mapserver.gis.umn.edu/bugs/show_bug.cgi?id=1743

http://mapserver.gis.umn.edu/bugs/show_bug.cgi?id=1841

请注意,这一问题可能很难复制,信贷去塔玛斯指出它。

三。拟议实施

为解决第2.1、2.2和2.3项所示问题,本循环贷款建议:

  1. 作为容器或内容的每个mapscript包装器对象都将增加其mapscript语言类,以维护对所包含对象的引用,以及在可用时对容器对象的引用。这些引用允许GC对哪些对象可以被释放做出更明智的决定。从现在起,我们将只为这个RFC的目的,调用给 cache 这些参考文献。

  2. 必须包装getter,以便它们在 cache 首先,必须包装setter/mutator,以便它们将引用保存在 cache 最新更改时间

  3. 当对象具有非空父对象时,其内存必须为 disowned (用swig术语)和 owned 当它从父级移除时

此RFC应适用于所有mapscript语言(经过必要的修改)。Perl或Java的例子是因为作者熟悉这些语言。

下面的小节将更详细地描述上述三个项目。第3.4和3.5小节为layerObj类提供了一个实现示例。请注意,在以下内容中,我们将分析的范围限制为类/层关系。

3.1添加对mapscript包装对象的引用

将修改mapscript对象,使其保持对添加到其中的其他mapscript对象的引用,或者将其添加到中,就像C结构一样。

例如,对于layerObj,layerObj类将被扩展为包含

  • 对包含层的mapobj的引用

  • 类对象数组

这些更改的目的是,每当将类添加到层时,相应的对象也将添加到类数组( 隐藏物) . 当ClassObj作为结果返回给getClass时也是如此。通过这样做,宿主语言了解了这些对象之间的关系网,并解决了早期的垃圾收集问题。

如果我们将getClass方法修改为在类数组中查找第一个对象,那么对象的相等性/标识问题也会得到解决。 隐藏物) 只有在C数据结构之后,如上图所示。

3.2保持 cache 与C内部结构同步

为了使上一项中描述的机制能够工作,必须修改mapscript对象和本机代码中的某些函数,以便mapscript对象和C数据结构保持同步。

回到layerobj的例子 插入类, getClassremoveClass 必须修改方法以保留 cache 与C数据结构同步。当前方法将通过使用特定的类型映射进行修改。还需要修改构造函数来存储对mapobj的引用。最后,实际执行复制和插入操作的本机代码必须修改为只执行插入操作(layerObject.c,第52行,函数 MnSerSert类) .

只要可能,API将保持向后兼容。

3.3 Swig为具有非空父对象分配的不一致内存

tamas(bug 1743)建议的补丁必须被扩展,这样当一个层插入到一个映射中并移植到所有mapscript语言时,内存也会被拒绝。

3.4 Java:LauleObjo的MpScript代码示例

layerObj(javamodule.i)的代码示例:::

/*
   Modified constructor according to:
   - bug#1743, item 3.3
   - cache population and sync, item 3.2
*/
%typemap(javaconstruct) layerObj(mapObj map) %{ {
        this($imcall, true);
        if (map != null) {
                this.swigCMemOwn = false;
                /* Store reference in field member*/
                this.map=map;
                /* Add myself to the layers array of the map */
                map.layers[this.getIndex()];
        }
}
%}

%typemap(javaout) int insertClass {
        // call the C API, which needs to be modified
        // so that the classObj is not copied anymore
        int actualIndex=$jnicall;
        classes[actualIndex]=classobj;
        // disown the classObj just inserted, item 3.3
        classobj.swigCMemOwn=false;
        return actualIndex;
}

%typemap(javaout) classObj *getClass {
                if (classes[i]!=null)
                        return classes[i];

                long cPtr = $jnicall;
                classObj result = (cPtr == 0) ? null : new classObj(cPtr, false);

                if (result!=null)
                        classes[i]=result;
                return result;
}

%typemap(javacode) layerObj %{
        /* an array of classes to keep referenced objects alive and prevent
           their garbage collection, item 3.1
        */
        classObj[] classes=new classObj[mapscriptConstants.MS_MAXCLASSES];
        /* same for the map, item 3.1
        */
        mapObj map=null;
%}

3.5 perl:layerObj的mapscript代码示例

layerObj(plmodule.i)的代码示例:::

%feature("shadow") ~mapObj() %{
sub DESTROY {
        return unless $_[0]->isa('HASH');
        my $self = tied(%{$_[0]});
        return unless defined $self;
        delete $ITERATORS{$self};
        if (exists $OWNER{$self}) {
                mapscriptc::delete_mapObj($self);
                delete $OWNER{$self};
        }
        delete $mapscript::MAPLAYERS{$self};
}
%}

%feature("shadow") layerObj(mapObj *map)
%{
sub new {
        my $pkg = shift;
        my $self = mapscriptc::new_layerObj(@_);
        bless $self, $pkg if defined($self);
        if (defined($_[0])) {
                $self->DISOWN();
                mapscript::LAYER_ADD_MAP_REF($self, $_[0]);
                mapscript::MAP_ADD_LAYER_REF($_[0], $self);
        }
        return $self;
}
%}

%feature("shadow") ~layerObj()
%{
sub DESTROY {
        return unless $_[0]->isa('HASH');
        my $self = tied(%{$_[0]});
        return unless defined $self;
        delete $ITERATORS{$self};
        if (exists $OWNER{$self}) {
                mapscriptc::delete_layerObj($self);
                delete $OWNER{$self};
        }
        delete $mapscript::LAYERMAP{$self};
}
%}

%perlcode %{
        %LAYERMAP={};
        sub LAYER_ADD_MAP_REF {
                my ($layer, $map)=@_;
                #print "MAP key=" . tied(%$layer) . "\n";
                $LAYERMAP{ tied(%$layer) }=$map;
        }

        %MAPLAYERS={};
        sub MAP_ADD_LAYER_REF {
                my ($map, $layer)=@_;
                my $layers=$MAPLAYERS{ tied(%$map) };
                if (defined($layers)) {
                        $layers->{$layer->{index}}=$layer;
                } else {
                        $layers={};
                        $layers->{$layer->{index}}=$layer;
                }
                $MAPLAYERS{tied(%$map)}= $layers;
        }

        sub map_get {
                my $layer=@_[0];
                #print "GET MAP key" . $layer . "\n";
                if ( $LAYERMAP{ $layer } ) {
                        return \%{$LAYERMAP{$layer}};
                }
                return mapscriptc::layerObj_map_get(@_);
        }

        ##################
        # DEBUGGING ONLY #
        ##################
        sub getLayerFrom {
                my ($map, $idx)=@_;
                return $MAPLAYERS{tied(%$map)}->{$idx};
        }

        sub getMAPLAYERS {
                return \%MAPLAYERS;
        }
%}

注意:这个例子实现了一个双向的 cache (map->layers和layer->map),但这在基于Perl引用计数的GC中不起作用,并且会导致内存泄漏。为了解决这个限制,PerlMapScript将只实现 cache 如4.3所述。

第四章。实施计划

对于大多数MaScript(Java、CpHARP、Perl和Python),在SWIG中有足够的功能来实现这个RFC中描述的特性。对于Ruby,我们可能需要走另一条路,实现*%trackobjects*功能。至于TCL,我目前不知道是否有可能。

下面的两个部分详细描述了所需的swig-mapscript特性(代码注入和构造函数定制)。每种语言都会得到一个特定的部分来处理其自身的特性

4.1检查swig mapscript功能:%javacode

swig为以下语言提供了相当于*%javacode*:

  1. Perl到%PerlCode

  2. python通过%pythoncode

  3. CSharp通过%CScode

  4. Ruby的包装对象中根本没有任何Ruby代码

  5. swig tcl不支持%tclcode构造

这个swig构造将用于在包装器中注入3.1中描述的引用的定义和包装器方法。

4.1检查swig mapscript功能:构造函数自定义

用于包装和自定义MaScript对象的结构体(项目3.3)的%CSCuto只能在CSHARP和Java中使用。应该可以在python中使用*%pythonprepend*或*%pythonprepend*模拟其行为,在perl中使用*%perlcode*或*%feature(“shadow”)*模拟其行为。

当对象引用有效的父对象(bug 1743)时,此swig构造将用于拒绝swig分配的内存。

4.2 Java和C锐

SWIG Java和SWIG CSARP共享一个共同的基础,因此非常相似。SWIG Java构造的名称可以通过改变 java 前缀为 cs (即 javacode 在里面 CSC码, javaconstruct 在里面 csconstructjavaout 在里面 出库) .

实施应完全遵循该循环贷款。

4.3珀尔

在上面的示例中,大多数Perl定制都可以使用 shadow 构造。

该实现将在以下方面与RFC不同:

1)Perl包装器将只在一个方向维护引用(layer->map或通常包含的->container),而不是因为Perl GC的实现(http://www.perl.com/doc/manual/html/pod/perlobj.html)而同时维护这两个方向。如果我们有循环引用 island 对象通常不会被垃圾收集。

2)包装 layerObj->{{map}} invocation we must edit the mapscript.pm 手动文件。为了克服这个限制并自动查找/替换,我们将使用Perl pies 在makefile中,如下两个:::

perl -pi -e "s/\*swig_map_get = \*mapscriptc::layerObj_map_get/\*swig_map_get = \*mapscript::map_get/" mapscript.pm
perl -pi -e "s/\*getMap = \*mapscriptc::layerObj_getMap/\*swig_map_get = \*mapscript::map_get/" mapscript.pm

4.4 Python

python在swig中享有一级支持,因此RFC应该按照描述的方式实现。与Perlpython的gc不同的是,gc可以查找和释放对象的循环(http://arctrix.com/nas/python/gc/)。

4.5红宝石

需要调查,我们可能需要使用rb_gc_ux函数来标记对象并防止其垃圾收集,或者使用*%trackobjects*。Ruby不会实现这个RFC。

4.6 TCL

需要调查和TCL专家。在编写本文时,TCL可能不会实现这个RFC。

5.1实施检查表

下表将用于跟踪此RFC的实现状态。每个mapscript对象都有一个表,当一种语言为一个给定的对象实现了这个rfc时,维护人员将用以下标记之一填充相对单元格:

  • >如果 cache 包装器已在父-子方向实现

  • <if cache 包装器已经在child->parent方向实现

  • <如果 cache 包装机已经在两个方向上实现了

  • 如果对象被拒绝,则为加号(+)

  • 如果对象已拥有,则为减号(-)

5.2 MAPOBJ

方法

爪哇

C#

珀尔

Python

tcl

ruby

GET符号集

获取字体集

图形标签缓存

获得范围

setSaved_extent

getSaved_extent

地球生物色

获取输出格式

获取引用

获取标尺

盖特传奇

获取查询文件

获取Web

获取配置

插入层

遥控器

获得层

GETLay-BelyNAME

准备图像

设置输出格式

图纸查询

拉锯传奇

牵引标尺

图纸参考图

获取标签

下标签

按名称获取输出格式

追加输出格式

删除输出格式

5.3层对象

方法

爪哇

C#

珀尔

Python

tcl

ruby

layerobj(构造函数)

获得地图

获取网站

获取元数据

克隆层

插入类

卸货舱

下一个形状

获取特征

获取形状

获得结果

获取类

获得结果

附加特征

获得范围

5.4类对象

方法

爪哇

C#

珀尔

Python

tcl

ruby

Classobj(构造函数)

获取标签

获取元数据

获得层

克隆

创建Legendicon

拖拉图标

获取样式

插入样式

重塑风格

5.5韦伯

方法

爪哇

C#

珀尔

Python

tcl

ruby

Webobj(构造函数)

获得地图

获得范围

设置范围

获取元数据

5.5样式对象

对于StyleObj,当从容器对象中提取它们时,就足以拒绝它们。不需要添加指向容器对象的引用。

方法

爪哇

C#

珀尔

Python

tcl

ruby

StyleObj(构造函数)

设置颜色

获取颜色

获取背景颜色

后退背景颜色

获取输出颜色

设置输出颜色

获得颜色

设置最小颜色

获得最大颜色

设置最大颜色

克隆

5.6标签

对于labelobjs来说,当它们从容器对象中提取时,就足以拒绝它们。不需要添加指向容器对象的引用。

方法

爪哇

C#

珀尔

Python

tcl

ruby

Labelobj(构造函数)

获取颜色

设置颜色

设置输出颜色

获取输出颜色

设置阴影颜色

获得阴影颜色

获取背景颜色

后退背景颜色

后退地面阴影颜色

获取背景阴影颜色

5.7哈希表obj

对于hashtableobj,当从容器对象(即layerobj)中提取它们时,就足以使它们脱离。不需要添加指向容器对象的引用。

方法

爪哇

C#

珀尔

Python

tcl

ruby

hashTableObj(构造函数)

5.8彩色obj

对于colorobj,当从容器对象中提取它们时,就足以使它们不受支持。不需要添加指向容器对象的引用。

方法

爪哇

C#

珀尔

Python

tcl

ruby

colorobj(构造函数)

5.9图像对象

对于ImageObjs,当从容器对象中提取它们时,足够拥有它们。不需要添加指向容器对象的引用。

方法

爪哇

C#

珀尔

Python

tcl

ruby

ImageObj(构造函数)

5.10形状对象

对于shapeobjs,当它们从容器对象中提取或添加时,足够正确地设置所有权。不需要添加指向容器对象的引用。

方法

爪哇

C#

珀尔

Python

tcl

ruby

shapeobj(构造函数)

盖特林

得界

得到

添加

克隆

复制

缓冲区

凸壳

边界

获得形心

联合

交叉

差异

共鸣

5.11线对象

对于LineObjs,当它们从容器对象中提取或添加时,足够正确地设置所有权。不需要添加指向容器对象的引用。

方法

爪哇

C#

珀尔

Python

tcl

ruby

LineObj(构造函数)

获得点

得到

添加

设置

5.12点

对于pointobjs,当它们从容器对象中提取或添加时,足够正确地设置所有权。不需要添加指向容器对象的引用。

方法

爪哇

C#

珀尔

Python

tcl

ruby

PointObj(构造函数)

托哈佩

5.13符号

对于symbolsetobjs,不需要添加指向地图的引用。

方法

爪哇

C#

珀尔

Python

tcl

ruby

符号etobj(构造函数)

设置符号(符号obj值)

获取符号

获取符号(int i)

获得符号符号

指数

附录符号

删除文件

5.14符号对象

对于Symbolobjs,当它们从容器对象中提取或添加时,就足以正确设置所有权。不需要添加指向容器对象的引用。

方法

爪哇

C#

珀尔

Python

tcl

ruby

符号obj(构造函数)

设定点

获取点

地理信息

缝合术

6。公开问题

从一个映射中提取一个对象,然后将其添加到另一个映射中,可能会导致segfault。解决方案是从第一个映射中删除它,然后在第二个映射中添加它,或者在更新对其父映射的引用并退出(抛出和异常)之前执行检查。

第七章。状态

RFC于2007年1月10日开放征求意见,发表了一篇关于mapserver-dev的文章。在讨论了mapserver-dev之后,RFC正在进行修订。