CTS测试框架 -- 命令调度

1.CommandScheduler启动

CommandScheduler本身也是一个线程,是在Console线程启动时启动的,作为命令调度的线程,主要作用就是检查本身的CommandQueue中是不是有需要处理的command,进行调度不至于多命令或者多设备时出现混乱,以及启动真正的命令执行线程。

public void run() {
    assertStarted();
    try {
        IDeviceManager manager = getDeviceManager();
        startRemoteManager();
        // 在启动的第一时间先countDown,此时main线程就会被释放,就退出了
        mRunLatch.countDown();
        // add a listener that will wake up scheduler when a new avail device is added
        manager.addDeviceMonitor(new AvailDeviceMonitor());
        // 进入了循环处理命令,直到输入了退出命令
        while (!isShutdown()) {
            // 每过30s起来看看是不是有需要调度的命令
            mCommandProcessWait.waitAndReset(mPollTime);
            checkInvocations();
            processReadyCommands(manager);
            postProcessReadyCommands();
        }
        // 退出循环之后代表程序即将退出了,进行一些清理工作
        mCommandTimer.shutdown();
        manager.terminateDeviceRecovery();
        manager.terminateDeviceMonitor();
        CLog.i("Waiting for invocation threads to complete");
        waitForAllInvocationThreads();
        closeRemoteClient();
        if (mRemoteManager != null) {
            mRemoteManager.cancelAndWait();
        }
        exit(manager);
        cleanUp();
        CLog.logAndDisplay(LogLevel.INFO, "All done");
    } finally {
        // Make sure that we don't quit with messages still in the buffers
        System.err.flush();
        System.out.flush();
    }
}

2.命令调度

在CommandScheduler的循环中去不断的看是否有新的命令,前面添加命令的时候,有notifyAll,以及上一个命令执行完毕等,当需要有命令被调度时都会有notifyAll去及时唤醒CommandScheduler开始干活。

public synchronized void waitAndReset(long maxWaitTime) {
    waitForEvent(maxWaitTime);
    reset();
}

// 这里是等待命令的逻辑
// 大致逻辑就是每过30s就看看是不是有需要调度的命令
public synchronized boolean waitForEvent(long maxWaitTime) {
    if (maxWaitTime == 0) {
        return waitForEvent();
    }
    long startTime = System.currentTimeMillis();
    long remainingTime = maxWaitTime;
    while (!mEventReceived && remainingTime > 0) {
        try {
            wait(remainingTime);
        } catch (InterruptedException e) {
            CLog.w("interrupted");
        }
        remainingTime = maxWaitTime - (System.currentTimeMillis() - startTime);
    }
    return mEventReceived;
}

调度processReadyCommands

protected void processReadyCommands(IDeviceManager manager) {
    Map scheduledCommandMap = new HashMap<>();
    // 命令调度是同步的,但是命令执行不是
    // 也就是说在调度符合的情况下,是可以开启多个命令执行的线程的
    // 即可以有多条命令同时运行
    synchronized (this) {
        Collections.sort(mReadyCommands, new ExecutableCommandComparator());
        Iterator cmdIter = mReadyCommands.iterator();
        while (cmdIter.hasNext()) {
            // 遍历mReadyCommands取出命令
            ExecutableCommand cmd = cmdIter.next();
            IConfiguration config = cmd.getConfiguration();
            // 创建命令context
            IInvocationContext context = new InvocationContext();
            context.setConfigurationDescriptor(config.getConfigurationDescription());
            Map devices = 
            // 申请设备
            allocateDevices(config, manager);
            // 如果能申请到设备,说明当前命令是可以执行的
            if (!devices.isEmpty()) {
                // 从ready Queue中移除
                cmdIter.remove();
                // 添加到正在执行的queue
                mExecutingCommands.add(cmd);
                context.addAllocatedDevice(devices);
                // 记录正在执行的命令的map
                scheduledCommandMap.put(cmd, context);
                mUnscheduledWarning.remove(cmd);
            } else {
                // 没有空闲的设备时提示警告
                if (!mUnscheduledWarning.contains(cmd)) {
                    CLog.logAndDisplay(LogLevel.DEBUG, "No available device matching all the "
                            + "config's requirements for cmd id %d.",
                            cmd.getCommandTracker().getId());
                    System.out.println(
                            String.format(
                                    "The command %s will be rescheduled.",
                                    Arrays.toString(cmd.getCommandTracker().getArgs())));
                    mUnscheduledWarning.add(cmd);
                }
            }
        }
    }
    // 这里真正开始执行
    for (Map.Entry cmdDeviceEntry : scheduledCommandMap
            .entrySet()) {
        ExecutableCommand cmd = cmdDeviceEntry.getKey();
        // 开启命令执行的新线程去执行命令
        startInvocation(cmdDeviceEntry.getValue(), cmd,
                new FreeDeviceHandler(getDeviceManager()));
        // 如果命令是循环模式,则再加入队列进行调度
        if (cmd.isLoopMode()) {
            addNewExecCommandToQueue(cmd.getCommandTracker());
        }
    }
}

开始执行命令的线程 InvocationThread

private void startInvocation(
        IInvocationContext context,
        ExecutableCommand cmd,
        IScheduledInvocationListener... listeners) {
    initInvocation();
    // Check if device is not used in another invocation.
    throwIfDeviceInInvocationThread(context.getDevices());
    CLog.d("starting invocation for command id %d", cmd.getCommandTracker().getId());
    final String invocationName = String.format("Invocation-%s",
            context.getSerials().get(0));
    // 初始化InvocationThread
    InvocationThread invocationThread = new InvocationThread(invocationName, context, cmd,
            listeners);
    logInvocationStartedEvent(cmd.getCommandTracker(), context);
    // 开启命令执行的线程
    invocationThread.start();
    addInvocationThread(invocationThread);
}

命令执行 InvocationThread.run
InvocationThread类的run方法开始执行,也就代表一个命令的真正执行就开始了,其实命令执行主要还是把前面封装好的Configuration中的组件拿出来,协作运行,主要还是执行其中最重要的test标签下的类中的模板方法,也就是说,test组件虽然是自己实现的,但是是遵循了模板接口的,命令的执行本质就是讲组件取出来协作运行,然后调用模板方法,也就走到了真正test组件的自定义要执行的方法。
在run方法中,其实做的事情不多,就是调用了初始化时创建的TestInvocation对象的invoke方法

instance.invoke(mInvocationContext, config,
        new Rescheduler(mCmd.getCommandTracker()), mListeners);

组件取出运行都在TestInvocation中,这个类功能很多,承担了整个框架的大部分组件的实际运行。

3.总结

命令的调度CommandScheduler的作用主要负责命令以及设备之间的调度,因为一条命令支持多设备运行,也可以多个设备同时运行不同的命令,必须有一个调度的角色去处理,CommandScheduler通过其内部的几个队列以及获取当前的设备状态完成调度,保证每条命令都能有条不紊的执行。

你可能感兴趣的:(CTS测试框架)