DEV Community

HarmonyOS
HarmonyOS

Posted on

Safe Database Initialization and Access in Multi-Threaded Mode

Read the original article:Safe Database Initialization and Access in Multi-Threaded Mode

Context

Failed to initialize the database and obtain the context in multi-threaded mode when using relationalStore.getRdbStore(). The global cache context in multi-threading is empty, leading to a ReferenceError such as:

relationalStore.getRdbStore(SseSdk.getConfig()?.getContext()!, this.config)
Enter fullscreen mode Exit fullscreen mode

Error message:

ReferenceError: xxxx is not initialized
Enter fullscreen mode Exit fullscreen mode

Description

In multi-threaded scenarios, developers may attempt to initialize and access the database context from worker threads. However, getRdbStore does not support direct multi-threaded concurrent operations. This results in initialization exceptions and prevents safe access to the relational database.

Solution / Approach

  • Important Note: getRdbStore currently supports only Promise and Callback asynchronous methods, not direct multi-threading.
  • The recommended approach is to:
    • Use async/await syntax to encapsulate database operations.
    • Create and manage database operations through worker threads while passing the context properly.
    • Use registerGlobalCallObject to share database objects between the main thread and worker threads safely.

Implementation Example:

Rdb.ets

export default class Rdb {
  rdbStore?: relationalStore.RdbStore;

  constructor(storeName: string, context: Context) {
    const STORE_CONFIG: relationalStore.StoreConfig = {
      name: storeName,
      securityLevel: relationalStore.SecurityLevel.S1,
    };

    relationalStore.getRdbStore(context, STORE_CONFIG, 
      (err: BusinessError, rdbStore: relationalStore.RdbStore) => {
        this.rdbStore = rdbStore;
        if (err) {
          console.error(`Get RdbStore failed, code: ${err.code}, message: ${err.message}`);
          return;
        }
        console.info(`Get ${storeName} RdbStore successfully.`);
      }
    )
  }

  CreateTable() {
    // Define table creation logic here
  }

  // Additional DB operations...
}
Enter fullscreen mode Exit fullscreen mode

Index.ets

const workerInstance = new worker.ThreadWorker("entry/ets/workers/Worker.ets");
let context = getContext()

@Entry
@Component
struct Index {
  build() {
    Column() {
      Button('Main thread creates Worker query').onClick(async () => {
        let rdbTest = await new Rdb('Student.db', context)
        await rdbTest.CreateTable()
        await rdbTest.InsertData("Lisa", 12, 90.0)
        workerInstance.registerGlobalCallObject("myObj", rdbTest)
        workerInstance.postMessage(4)
      })

      Button('Worker creates database').onClick(() => {
        workerInstance.postMessage(context)
      })
      Button('Worker creates table').onClick(() => {
        workerInstance.postMessage(1)
      })
      Button('Worker inserts data').onClick(() => {
        workerInstance.postMessage(2)
      })
      Button('Worker query data').onClick(() => {
        workerInstance.postMessage(3)
      })
    }
    .height('100%')
    .width('100%')
  }
}
Enter fullscreen mode Exit fullscreen mode

Worker.ets

import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';
import Rdb from '../pages/rdb';

const workerPort: ThreadWorkerGlobalScope = worker.workerPort;
let rdbTest: Rdb;

workerPort.onmessage = async (e: MessageEvents) => {
  if (e.data == 1) {
    await rdbTest.CreateTable()
  } else if (e.data == 2) {
    await rdbTest.InsertData("Lisa", 12, 90.0)
  } else if (e.data == 3) {
    await rdbTest.queryKeySync()
  } else if (e.data == 4) {
    try {
      workerPort.callGlobalCallObjectMethod("myObj", "queryKeySync", 0)
    } catch (error) {
      console.error(`worker: error code ${error.code}, message: ${error.message}`)
    }
  } else {
    rdbTest = await new Rdb('Student.db', e.data)
  }
}

workerPort.onmessageerror = (e: MessageEvents) => {
  console.log('onmessageerror', e)
}

workerPort.onerror = (e: ErrorEvent) => {
  console.log('worker error', e)
}
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • relationalStore.getRdbStore does not support concurrent multi-threaded initialization.
  • Use async/await to manage asynchronous database operations.
  • Database operations in worker threads should rely on context passing and global call object registration (registerGlobalCallObject).
  • This ensures safe and consistent access to the relational database in multi-threaded environments.

Additional Resources

https://developer.huawei.com/consumer/en/doc/harmonyos-references-V5/js-apis-data-relationalstore-V5#relationalstoregetrdbstore

Written by Mehmet Algul

Top comments (0)