OpenVOS操作系统提供了一个高级应用程序编程接口(API),整体上使系统编程变得简单。但有时,这种简单反而可能带来问题——因为对s$…例程的简单子程序调用,可能隐藏了大量复杂性。
这是不定期系列文章的首篇,旨在提醒您注意此类陷阱,以便在应用程序设计中规避它们。
第25行消息的弊端
在终端第25行(或底部任意行)写入消息的功能,允许用户(或运行中的程序)向其他用户通报异常状况。命令行接口为send_message命令。该命令及用户程序最终调用s$send_message API子程序。 该调用的参数包括接收方的用户名、目标模块名(即用户可能登录的模块)、通知文本以及若干用于修改操作细节的标志。特权调用者可指定发送方名称;否则默认采用发送进程的用户名。
目前看来情况似乎很简单。由于该操作需要访问其他用户拥有的终端,内核会将请求发送到目标模块上TheOverseer进程的服务器队列中。通常情况下,发送进程会等待消息传递状态,但也可以选择不等待此响应。
监视器进程负责追踪所有模块的登录进程及其使用的终端设备。 当收到发送消息的请求时,它会搜索进程与设备列表,对于每个与接收方名称参数匹配的用户名,都会将端口附加到该进程的终端设备上,打开端口,将消息写入通知行,关闭端口,然后分离端口。若存在多个进程匹配接收方名称参数,则对每个进程重复此附加/打开/写入/关闭/分离的循环操作。
好的——这比我们最初讨论的要复杂一些。进一步探究后发现,系统中的任意模块都可能托管登录进程,其中真实终端是另一系统/模块上的设备,该设备通过Open StrataLink(OSL)跨网络登录(使用login –module命令)。 现在,对该终端看似简单的连接/打开/写入/关闭/断开操作,必须涉及OSL进程和向远程模块的网络远程过程调用,其开销远高于模块内部处理。
当接收方用户名user_name被指定为可能匹配多个或所有登录用户的通配符名称时,效果会得到放大。若目标模块同样被指定为通配符名称,则效果进一步放大。最坏情况是向用户“*.*”(所有用户、所有组)发送消息至模块“*”(当前系统所有模块)。
实施
s$expand_module_starname(target_module) => module_list;
foreach module_name in module_list
send_overseer_request(module_name, send_message_request_data)
The message is sent via a server queue and OSL to TheOverseer process
on that module.
TheOverseer process on each receiving module then does:
foreach terminal_process in TheOverseer’s list of
login processes and sub-processes;
if (terminal_process user_name matches receiver star name)
if (messages_queued_for_device < 5)
queue it for the login device
else
reject the request (e$too_many_terminal_messages)
同时,最多可使用10个端口将队列中的消息发送至终端;
针对每个终端的每条消息:
附加端口
打开端口
s$control(…WRITE_SYSTEM_MESSAGE…)
关闭端口
分离端口
此处理在no_wait_mode模式下为每个终端执行,因此延迟(如跨网络的I/O请求)仅影响该终端的消息处理。
若任何消息在300秒内无法送达,则予以清除。
当应用程序使用此机制报告错误时,若遭遇错误洪流,便极易陷入报告泥潭——在等待其他消息处理与向用户终端发送过快以致无法理解的消息之间反复切换。
回避
既然你已经了解幕后情况,我能给你的建议是:
- 精心设计用户名和模块名的星号命名规则,确保消息仅触达必须接收的用户,避免误触其他用户。例如:John_Doe.* 或 *.Operations 或 John_Doe.SysAdmin。
- 考虑实施一个非用户名的通知列表,并分别发送每条通知。
- 将错误消息的详细信息记录在其他位置(例如错误日志),并使用 s$send_message 仅用于提示错误存在。
- 将通知频率限制在合理范围内(例如每分钟不超过一次)。
- 抑制任何与上次发送内容相同的讯息。新讯息会覆盖先前内容。
如有其他建议,请发表评论供大家参考。
