【WIP】对象卸载注意事项

1. 对象加载卸载方式

业务组件中关于对象的加载卸载有2种方式:

  • 方式1:通过mc.mdb.object_manage的on_add_object回调函数添加对象、on_delete_object回调函数删除对象
-- 代码示例
local object_manage = require 'mc.mdb.object_manage'

object_manage.on_add_object(self.bus, function(class_name, object, position)
    self.unit_manager:on_add_object(class_name, object, position)
    self.card_manager:on_add_object(class_name, object, position)
end)
  • 方式2:通过mc.orm.object指定类名直接管理对象的生命周期
-- 代码示例
local c_object = require 'mc.orm.object'
local c_network_adapter = c_object('NetworkAdapter')

2. 无需考虑对象卸载的场景

在BMC整个生命周期内必然存在的对象可以不考虑卸载:

  • 纯软件对象,典型的比如UpdateService

  • 与不可拆卸硬件强关联的对象,比如基于BMC flash抽象的NandFlash

3. 对象卸载时需要考虑的场景

对象卸载时需要考虑以下场景:

3.1 组件内对象对资源协作对象的引用

  • 在方式1的场景下,业务组件往往会将框架分发的资源协作对象引用到组件内部另外封装的对象上,并将该封装对象放到一个table中维护。当资源协作对象卸载时,需要注意同步删除table中对封装对象的引用。
-- 以GPU为例,资源协作对象卸载时删除组件内维护的对象
local function remove_ele_by_position(list, position)
    for i, obj in pairs(list) do
        if obj.position == position then
            table.remove(list, i)
            break
        end
    end
end

function gpu_service:on_delete_object(class_name, mds_obj, position)
    remove_ele_by_position(self.gpu_collection, position)
end
  • 另外要注意如果table是序列形式,通过置nil的方式删除元素会导致序列不连续,此时不能通过ipairs来遍历table

3.2 资源协作对象对设备树对象的引用

  • 设备树对象本质上是devmon服务管理的资源协作对象

  • 绝大部分设备树对象当前并未直接对接北向接口,而是由业务组件监听设备树对象重新生成资源协作对象。当设备树对象下树时,相应的资源协作对象也应当下树

-- 仍以GPU为例
function gpu_mgmt:on_del_device_obj()
    log:notice('[device_mgmt]on_del_device_obj start')
    -- 监听设备树对象下树
    comm_fun.set_interface_del_signal(self.bus, self.sig_slot, comm_defs.PCIE_GPU_CARD_DEVICE_PATH_PATTERN,
        comm_defs.PCIE_DEVICE_INTERFACE, function(path)
            self:del_obj(path)
        end)
end

function gpu_mgmt:del_obj(device_path)
    pcall(function()
        -- 删除资源树对象
        clsmgmt(comm_defs.GPU_CLASS_NAME):remove(resource_obj)
        -- 调用组件回调
        gpu_service.get_instance():on_delete_object(comm_defs.GPU_CLASS_NAME, resource_obj, position)
        self.objects[device_path] = nil
    end)
end

3.3 清理任务

  • 通过skynet.fork或skynet.fork_once创建的协程并没有被纳管到对象的生命周期中。当对象被删除时,这些协程仍然存在,需要通过其他方式退出。而方式2通过new_task方法创建的任务则没有这种问题
-- 反例
function c_optical_module:start_get_optical_info_task()
    skynet.fork_once(function()
        while true do
             -- 代码省略
            skynet.sleep(500)    -- 每5s更新光模块信息
        end
    end)
end
-- 正例
function c_optical_module:start_get_optical_info_task()
    self.tasks:new_task(
        'get optical module task ' .. tostring(self.NetworkAdapterId) .. '-' ..
        tostring(self.PortID)):loop(function(task)
        -- 代码省略
    end):set_timeout_ms(5000)     -- 每5s更新光模块信息
end

3.4 恢复默认

  • 对于以槽位角度抽象的硬件类(例如Drive/OnePower/CPU等,这些类往往会有一个Presence属性),当物理实体被拔出后,资源协作对象仍然存在,因此可以不用处理资源释放,但是应当将属性恢复成默认值,以恢复告警或保证北向接口显示正常

  • 在恢复默认值时,由于对象的属性可能涉及持久化,因此需要删除或修改对应保持在数据库的数据。注意与槽位相关的属性谨慎删除

-- 硬盘拔出后会恢复默认值,但是不会恢复点灯识别设置的属性(RefControllerId、SlotNumber、EnclosureId)
drive.on_presence_changed:on(function(is_presence)
    if is_presence then
        -- 省略
    else
        -- 拔盘需要做的处理
        log:notice('Disk%s del', drive.Id)
        set_drive_invalid_values(drive)
    end
end)
1 个赞