June 30
C语言本身没有自动垃圾收集机制,这让C程序员在使用堆内存时不得不小心翼翼,时刻担心因内存泄露造成灭顶之灾。但CICS环境提供了一些善巧方便,让C程序员也可以像JAVA程序员那样随意使用堆内存。这是一种叫做'CICS Task-Private Pool'的内存池机制,当启动一个任务时AS会自动分配一整块内存,当任务结束时AS自动回收。这样不仅可以避免调用malloc陷入trap的开销,更可以告别内存泄露的问题,下面是包装成C函数的代码:
void * xmalloc (size_t size ){
void * mem ;
int r ;
if(dfheiptr == NULL)EXEC CICS ADDRESS EIB(dfheiptr);
EXEC CICS GETMAIN SET(mem) FLENGTH(size) RESP(r);
return r == DFHRESP(NORMAL) ? mem : NULL ;
}
void * xcalloc (size_t size ){
void * mem ;
int r ;
if(dfheiptr == NULL)EXEC CICS ADDRESS EIB(dfheiptr);
EXEC CICS GETMAIN SET(mem) FLENGTH(size) INITIMG(0) RESP(r);
return r == DFHRESP(NORMAL) ? mem : NULL ;
}
void xfree(char * mem){
EXEC CICS FREEMAIN DATA(mem);
}
在此基础上,我们可以构建更高级的通用数据结构。如动态数组,Hash表等。
Apache的APR库有这些数据结构的实现,而且这个库同样使用内存池机制,
我们只需要将代码中的APR订制内存池换成CICS私有内存池即可。例如将
apr_hash_t * ht = apr_palloc(pool, sizeof(apr_hash_t));
替换成
apr_hash_t * ht = xmalloc(sizeof(apr_hash_t));
下面的代码展示了如何使用Hash数据结构实现O/R映射:
trace_t * get_trace(hash_t * ht , char * id ){
trace_t * ptrace = NULL ;
if(( ptrace = hash_get( ht , id , HASH_KEY_STRING) ) == NULL){
//从CICS Task-Private Pool中分配内存,以备存储数据
ptrace = xmalloc(sizeof(ptrace_t));
if(ptrace == NULL)return NULL ;
//从数据库加载数据
int r = trace_init(ptrace , id );
if(r)return NULL ;
//将数据存入Hash中
hash_set(ht, ptrace->id , HASH_KEY_STRING , ptrace);
}
return ptrace ;
}
May 19
项目使用三层架构。中间层是嵌入C开发的CICS程序,前台使用PowerBuilder做展示。因为中间层的存在,使得数据驱动的验收测试成为可能。即一个中间层应用程序既被PB调用,也被测试脚本调用。由ActiveRecord负责与数据库的交互.RSpec作为测试框架。测试三步曲对应如下:
- Pre-conditions: 向数据库中插入测试需要的数据
- Event: 调用CICS服务(通过'win32ole'连接EasyCics)
- Post-conditions: 验证数据
在使用过程中也碰到一些问题,撮要如下:
- 数据库驱动ibm_db中将smallint一律视为boolean型,如果你的数据库确实将smallint作为小整数使用,需要在ibm_db_adapter.rb中simplified_type函数修改类型映射,将boolean改为integer.
- ActiveRecord默认不支持复合主键,需要使用第三方的composite_primary_keys.注意使用复合主键查询时参数应使用数组,如:@slab = Slab.find([ID_SLAB , 1])
而不是官方网页上写的@slab = Slab.find(ID_SLAB , 1)
BTW,使用复合主键时,调用ActiveRecord::reload方法会有异常 - 上回提到的时间戳问题
- 实践中经常需要判断数据库中的一条记录是否被调用的CICS服务修改过。(普通情况下所有的修改都通过 ActiveRecord,不会有第三方存在)。可以添加一个方法:
class ActiveRecord::Base
def untouched?
newvalue = self.class.find(self.id)
self.class.columns.map do |col|
case col.type
when :string
return false unless self.send(col.name).strip == newvalue.send(col.name).strip
when :timestamp
return false unless self.send(col.name).to_s == newvalue.send(col.name).to_s
else
return false unless self.send(col.name) == newvalue.send(col.name)
end
end
true
end
end
在RSpec中就可以这样写:
@slab.should be_untouched
至少是形似英语了
- 测试后需要销毁初始插入的数据,以使测试过程可以重复。这一步可以利用Ruby强大的反射机制:
after :each do
instance_variables.each do |var|
if instance_eval(var).class.superclass == ActiveRecord::Base
instance_eval(var).destroy
end
end
end
April 23
对我们的数据库使用ActiveRecord做映射
在插入数据时报时间格式错误
查看column信息,发现timestamp类型的列@default值为nil
我们数据库里的时间戳默认值设定为'1900-01-01-00.00.00'
估计是因为该时间在Epoch('1970-01-01-00.00.00')之前,Ruby的Time类无法处理
换成最近的时间如'2000-01-01-00.00.00',果然一切OK
因为修改数据库代价较大,而又不希望写程序时对所有时间戳格式的字段手动赋值一遍
只好修改ActiveRecord,在timestamp.rb的create_with_timestamps方法下添加:
t0 = Time.at(0).utc
self.class.columns.map do |col|
write_attribute(col.name , t0) if col.type == :timestamp and self.send(col.name).nil?
end
另外,ActiveRecord在创建和保存记录时自动更新名为created_at,updated_at的时间字段
如果系统里用的不是这两个名字,也可以通过修改timestamp.rb来实现。
tmstps = ['tmstp_crea' , 'tmstp_chg']
self.class.columns.map do |column|
if column.type == :timestamp and self.send(column.name).nil?
write_attribute(column.name , tmstps.include?(column.name) ? t : t0)
end
end
March 16
一. 使用共享库
如果使用静态链接,每个服务的实现中都包含一份easycics的实现.显然是对存储资源的浪费;且一旦easycics库修改,
需要付出较大的代价重新编译部署,使用共享库则可以避免这些问题.下面是将easycics编译为共享库的makefile:
#Platform - AIX 5.3 ; XL C 9
easycics.so : easycics.o
xlc -o $@ -bI:/usr/lpp/cics/lib/cicsprC.exp -bE:easycics.exp -bM:SRE -bnoentry easycics.o
easycics.o : easycics.c
xlc -O2 -qarch=auto -c -I/usr/lpp/cics/include easycics.c
easycics.c : easycics.ccs
cicstran -lC easycics.ccs
其中easycics.exp是导出符号列表,可以通过下面的Perl脚本解析easycics.h生成
open (HEADER, 'easycics.h' ) or die "$!";
open (EXPFILE, '>easycics.exp' ) or die "$!";
for (<HEADER>) {
print EXPFILE "$1\n" if (/_stdcall\s+(\w+)/ ) ;
}
下面是对测试程序分别用两种方式编译,产生的文件大小对比(size -f):
| 静态链接 | 32870(.text) + 41850(.data) + 2780(.bss) + 1792(.loader) = 79292 |
| 动态链接 | 1473(.text) + 263(.data) + 0(.bss) + 860(.loader) = 2596 |
可以看到,两者大小有30倍的差距!!
TXSeries每个AS只用一个线程运行客户程序,其他线程用于侦听事件,与RM建立XA等系统任务。easycics并非线程安全,但TXSeries对客户来说实际相当于单线程,故可以使用。
二. 去除cicstran
一般对使用嵌入SQL的CICS服务的编译过程如下:
- 将XXXX.sqc经DB2预编译器翻译为XXXX.c
- 将XXXX.c重命名为XXXX.ccs
接着调用cicstcl集成编译工具,实际上分为两步: - 调用cicstran转换工具将XXXX.ccs变为XXXX.c
- 调用C编译器完成最后的编译链接
其中cicstran工具用于转换EXEC CICS语句,而easycics库实际上屏蔽了应用程序对EXEC CICS的直接调用.经过对照,cicstran对于使用easycics库的程序,仅仅是添加了两个头文件(<cics_api.h>以及<cics_eib.h>),并在main函数下添加了一个函数调用:
int cics_api_temp_var = cics_api_edf_init_c_extended(0, &CicsArgs);
该函数是用来设置CicsArgs,决定是否启动CICS的EDF(Execution Diagnostic Facility)。因为CicsArgs是静态变量,easycics库中有单独的一份实例,此处调用没有实际效果,可以忽略. 所以,cicstran转换步骤对于没有EXEC CICS调用的程序是多余的. 编译过程可以忽略2,3两步。这样就减少了cicstcl的进程调用开销以及cicstran的文本转换过程,编译速度有很大的提高。此外,cicstcl所依赖的两个环境变量CCFLAGS和USERLIB也不再需要。makefile可以精简成下面的形式:
include ${MAKERULES}
serv := XXXX
$(serv): $(serv).c
$(serv).c : $(serv).sqc
February 22
Ask yourself questions like:
- "How can I make sure this problem goes away forever? "
- "How can I produce fewer bugs?"
- "How can I make it easier to fix the bugs I have?"
- "How can I make it easier to respond to change quickly?"
- "How can I make it easier to make my software fast enough?"
February 11
项目使用的的中间件及数据库平台全面升级
需要在新系统上重新构建和部署
迁移过程暴露了我们在SCM上的不成熟
例如,跳过集成流,将在开发流上编译出来的东西直接部署
造成无法找到和运行环境直接对应的版本(源码等于或新于运行版本)
相当麻烦
一个意外收获是改变了对Perl的观感
迁移过程中经常遇到文本替换,统计分析之类的任务
用Perl来做这类Quick And Dirty的事情非常顺手
例如统计共享库的调用,将dump -H的结果导出到变量中
再用RE解析此变量,最后将结果存入Hash结构
这几个步骤都有内建的机制支持,手挥目送一气呵成
印证了Larry Wall书中的比喻:Perl就像骆驼,并不养眼,但很实用
January 21
翻完《程序员》杂志SOA专辑上那帮'架构宇航员'的大作,倒是想起鲁迅的一篇文章:
他在药方上所开的却不是药名,而是“好药料”三个大字,以及一些唠唠叨叨的名医架子的“主张”。不错,谁也不能说医病应该用坏药料,但这张药方,是不必医生才配摇头。 《"好政府主义"》