SQLite3源码学习(16) test_vfs框架

test_vfs主要是用来做测试的,用来模拟文件系统出错的,代码实现在test_vfs.c里面。对于一些诸如机械故障、文件系统空间耗尽的错误是很难实际测出来的,test_vfsOS接口层和VFS之间插入一层测试的VFS用来模拟文件系统出错,进而验证SQLite对错误处理的稳定性。由Tcl脚本来控制模拟,在正常情况下还是调用原来的VFS

1.注册VFS

Tcl中使用格式如下:

testvfs VFSNAME ?SWITCHES?

testvfs的实现函数为testvfs_cmd(),在这个函数里创建了一个新的命令VFSNAME用来设置一些测试场景,SWITCHEStestvfs命令的一些选项,如:

-noshm:置1禁止共享缓存

-default:把tvfs_vfs设置为首选VFS

-fullshm:使用原VFS的共享缓存

注册时首先声明VFS接口函数,在tvfs_vfs结构体里

static sqlite3_vfs tvfs_vfs

pAppData记录原来默认的VFS

 Testvfs *p;                     /* New object */
  sqlite3_vfs *pVfs;              /* New VFS */

  zVfs = Tcl_GetString(objv[1]);//取得VFSNAME的名称
  nByte = sizeof(Testvfs) + (int)strlen(zVfs)+1;
  p = (Testvfs *)ckalloc(nByte);
  memset(p, 0, nByte);

p->pParent = sqlite3_vfs_find(0);//获取默认VFS

p->zName = (char *)&p[1];
memcpy(p->zName, zVfs, strlen(zVfs)+1);//设置当前vfs名称
pVfs->pAppData = (void *)p;//把信息存到pVfs->pAppData里

sqlite3_vfs_register(pVfs, isDefault);//注册VFS

在创建命令时,还把pVfs->pAppData的信息(即Testvfs *p的地址)传到了命令里供调用:

//声明
EXTERN Tcl_Command	Tcl_CreateObjCommand(Tcl_Interp *interp,
				const char *cmdName, Tcl_ObjCmdProc *proc,
				ClientData clientData,
				Tcl_CmdDeleteProc *deleteProc);
//创建新命令
Tcl_CreateObjCommand(interp, zVfs, testvfs_obj_cmd, p, testvfs_obj_del);
//命令函数,第1个参数即为上面传入的p
static int SQLITE_TCLAPI testvfs_obj_cmd(
  ClientData cd,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
)

2. Tcl命令参数

   令的回调函数是testvfs_obj_cmd,该命令中有多个执行参数如下:

● shm

假设注册VFS时新建的命令是tvfs,使用格式为:

tvfs shm FILE ?VALUE?

上述命令中,打开文件连接FILE对应的缓存,将其加入到一个数组对象中如果第4个参数存在,那么把VALUE对象数组的值拷贝到缓存,举例:

set blob [tvfs shm $file]

上述命令中新建一个数组对象,数组存放的是对应连接文件的缓存

Testvfs *p = (Testvfs *)cd;
/*搜索对应缓存*/
      for(pBuffer=p->pBuffer; pBuffer; pBuffer=pBuffer->pNext){
        if( 0==strcmp(pBuffer->zFile, zName) ) break;
      }

把缓存添加到数组对象,然后返回这个数组对象

      

 pObj = Tcl_NewObj();
      for(i=0; pBuffer->aPage[i]; i++){
        int pgsz = pBuffer->pgsz;
        if( pgsz==0 ) pgsz = 65536;
        Tcl_AppendObjToObj(pObj, Tcl_NewByteArrayObj(pBuffer->aPage[i], pgsz));//依次添加数组缓存
      }
      Tcl_SetObjResult(interp, pObj);//该命令返回的Tcl对象
      

假如blob对象的数组中的内容在Tcl中被修改,然后blob对象在该命令中以第4个参数被传入时,会把对象里的内容拷贝到缓存:

      

  if( objc==4 ){
        int n;
        u8 *a = Tcl_GetByteArrayFromObj(objv[3], &n);//数组长度
        int pgsz = pBuffer->pgsz;

        if( pgsz==0 ) pgsz = 65536;
        /*拷贝对象数组到缓存页*/
        for(i=0; i*pgszaPage[i], &a[i*pgsz], nByte);
        }
      }

● script

注册脚本的回调函数,保存在p->pScript,注册如下

tvfs script tvfs_cb  其中tvfs_cb是回调函数

 

● filter

VFS中有很多方法如:

static struct VfsMethod {
        char *zName;
        int mask;
      } vfsmethod [] = {
        { "xShmOpen",           TESTVFS_SHMOPEN_MASK },
        { "xShmLock",           TESTVFS_SHMLOCK_MASK },
        { "xShmBarrier",        TESTVFS_SHMBARRIER_MASK },
        { "xShmUnmap",          TESTVFS_SHMCLOSE_MASK },
        { "xShmMap",            TESTVFS_SHMMAP_MASK },
         ……
}

该命令对特定的方法设置过滤掩码

/*获取过滤的方法参数列表*/
      if( Tcl_ListObjGetElements(interp, objv[2], &nElem, &apElem) ){
        return TCL_ERROR;
      }
……
/*获取过滤方法的掩码*/
      for(i=0; imask = mask;//最后保存掩码

比如设置xShmOpenxShmLock方法的过滤掩码

tvfs filter xShmOpen xShmLock

那么只有这2个方法才能执行回调函数,如

 

  if( p->pScript && p->mask&TESTVFS_SHMOPEN_MASK ){
    /*判断xShmOpen掩码有没有设置,设置了才执行脚本的回调函数*/
    tvfsExecTcl(p, "xShmOpen", Tcl_NewStringObj(pFd->zFilename, -1), 0, 0, 0);
    if( tvfsResultCode(p, &rc) ){
      if( rc!=SQLITE_OK ) return rc;
    }
  }

● ioerrfullerrcantopenerr

3个命名用来设置模拟出错次数等

    

 case CMD_CANTOPENERR:
    case CMD_IOERR:
    case CMD_FULLERR: {
      TestFaultInject *pTest = 0;
      int iRet;

      switch( aSubcmd[i].eCmd ){
        case CMD_IOERR: pTest = &p->ioerr_err; break;
        case CMD_FULLERR: pTest = &p->full_err; break;
        case CMD_CANTOPENERR: pTest = &p->cantopen_err; break;
        default: assert(0);
      }
      iRet = pTest->nFail;//用来设置对象返回结果
      pTest->nFail = 0;//失败次数
      pTest->eFault = 0;//是否持续模拟出错
      pTest->iCnt = 0;//在出错前正常的次数
  /*接下来在命令中取得相关参数,代码从略*/
……

3. open

vfs打开的接口是tvfsOpen()函数,最主要的2个参数:

sqlite3_vfs *pVfs

用来获取与vfs关联的应用数据,从而调用脚本回调函数

Tcl_Obj *pId = 0;
Testvfs *p = (Testvfs *)pVfs->pAppData;
if( p->pScript && p->mask&TESTVFS_OPEN_MASK ){
    Tcl_Obj *pArg = Tcl_NewObj();
    Tcl_IncrRefCount(pArg);
 ……
    tvfsExecTcl(p, "xOpen", Tcl_NewStringObj(pFd->zFilename, -1), pArg, 0, 0);
    Tcl_DecrRefCount(pArg);
    if( tvfsResultCode(p, &rc) ){
      if( rc!=SQLITE_OK ) return rc;
    }else{
      pId = Tcl_GetObjResult(p->interp);//此处返回的对象一般作为脚本回调函数的传入参数
    }
  }

sqlite3_file *pFile

这是一个连接句柄,由PagerOpen分配空间,传入时将其转换为Testvfs类型,从而设置io方法等信息

#define PARENTVFS(x) (((Testvfs *)((x)->pAppData))->pParent)

  TestvfsFile *pTestfile = (TestvfsFile *)pFile;
  TestvfsFd *pFd;
  pFd = (TestvfsFd *)ckalloc(sizeof(TestvfsFd) + PARENTVFS(pVfs)->szOsFile);//为parent vfs分配连接句柄
  memset(pFd, 0, sizeof(TestvfsFd) + PARENTVFS(pVfs)->szOsFile);
  pFd->pShm = 0;
  pFd->pShmId = 0;
  pFd->zFilename = zName;
  pFd->pVfs = pVfs;
  pFd->pReal = (sqlite3_file *)&pFd[1];//此处跳过一个sizeof(TestvfsFd)长度的偏移,用来获得parent vfs的连接句柄
  memset(pTestfile, 0, sizeof(TestvfsFile));
  pTestfile->pFd = pFd;//绑定到连接句柄pFile上
rc = sqlite3OsOpen(PARENTVFS(pVfs), zName, pFd->pReal, flags, pOutFlags);//获取parent vfsd的句柄pFd->pReal
if( pFd->pReal->pMethods ){
    sqlite3_io_methods *pMethods;
    int nByte;

    if( pVfs->iVersion>1 ){
      nByte = sizeof(sqlite3_io_methods);
    }else{
      nByte = offsetof(sqlite3_io_methods, xShmMap);// 这个是求结构体成员的相对便宜地址的,宏展开为((size_t) &((sqlite3_io_methods *)0)->xShmMap)
    }

    pMethods = (sqlite3_io_methods *)ckalloc(nByte);
memcpy(pMethods, &tvfs_io_methods, nByte);
……
pFile->pMethods = pMethods;//绑定io_methods为tvfs_io_methods
}

4.错误模拟

如果Tcl里配置了错误信息,那么返回错误,否则正常调用parent vfsio方法,以下为open出错的模拟代码

static int tvfsInjectFault(TestFaultInject *p){
  int ret = 0;
  if( p->eFault ){//开启出错模拟
    p->iCnt--;//出错之前调用的次数
    if( p->iCnt==0 || (p->iCnt<0 && p->eFault==FAULT_INJECT_PERSISTENT ) ){
      ret = 1;
      p->nFail++;//记录出错次数
    }
  }
  return ret;
}

static int tvfsInjectIoerr(Testvfs *p){
  return tvfsInjectFault(&p->ioerr_err);//磁盘io错误
}

static int tvfsInjectFullerr(Testvfs *p){
  return tvfsInjectFault(&p->full_err);//磁盘空间不足错误
}
static int tvfsInjectCantopenerr(Testvfs *p){
  return tvfsInjectFault(&p->cantopen_err);//打开错误
}

  if( (p->mask&TESTVFS_OPEN_MASK) &&  tvfsInjectIoerr(p) ) return SQLITE_IOERR;
  if( tvfsInjectCantopenerr(p) ) return SQLITE_CANTOPEN;
  if( tvfsInjectFullerr(p) ) return SQLITE_FULL;


你可能感兴趣的:(SQLite)