Dynamsoft / capture-vision-react-native-samples

Dynamsoft Capture Vision React-Native SDK Samples
https://www.dynamsoft.com/capture-vision/docs/introduction/
Other
15 stars 7 forks source link

Error with Severe hang after multiple barcode readers #41

Closed badbeoti closed 10 months ago

badbeoti commented 1 year ago

스크린샷 2023-06-15 오후 4 54 04

It only happens on iOS(I use RN: iOS-13.0, device: 16.4)

Using package version "dynamsoft-capture-vision-react-native": "^1.1.7", "react": "18.2.0", "react-native": "0.71.7",

import React, { useRef, useState, useEffect } from 'react';
import { RouteProp, useNavigation, useNavigationState, useRoute } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AppStackParamList } from '../../route/routeDef';
import { IFile } from '../../constants/IFile';
import { imgUploadCall } from '../../helper/s3Upload';
import useCustomHeader from '../../hooks/useCustomHeader';
import styled from 'styled-components/native';
import LMText from '../../components/atoms/LMText';
import {
  DCVBarcodeReader,
  DCVCameraView,
  EnumBarcodeFormat,
  EnumTorchState,
} from 'dynamsoft-capture-vision-react-native';
import { BarcodeResult } from '../../node_modules/dynamsoft-capture-vision-react-native/js/BarcodeResult';
import codeMasterStore from '../../stores/codeMasterStore';
import loaderStore from '../../stores/loaderStore';
import Button from '../../components/atoms/button';
import { captureScreen } from 'react-native-view-shot';
import { Platform } from 'react-native';
import { observer } from 'mobx-react';

const Layout = styled.View`
  flex: 1;
  justify-content: space-between;
`;
const NotificationView = styled.View`
  align-items: center;
`;
const NotificationBox = styled.View`
  margin-top: 19px;
  background-color: ${({theme}) => theme.colors.white.base};
  border-radius: ${({theme}) => theme.base.borderRadius}px;
  width: 258px;
  padding: 12px 20px;
  align-items: center;
  justify-content: center;
`;
const NotificationText = styled(LMText)`
  font-size: ${({theme}) => theme.typography.size.s1}px;
  color: ${({theme}) => theme.colors.gray.base};
  text-align: center;
  line-height: 19.56px;
`;
const BottomView = styled.View`
  margin-bottom: 24px;
  padding: 0 ${({theme}) => theme.base.templatePadding}px;
`;
const LicenseText = styled.Text`
  position: absolute;
  top: 25%;
  right: 25%;
  font-size: ${({theme}) => theme.typography.size.l3}px;
  color: ${({theme}) => theme.colors.gray[300]};
`;
const ScanRegion = {
  regionTop: 40,
  regionLeft: 15,
  regionBottom: 60,
  regionRight: 85,
  regionMeasuredByPercentage: true,
};
const Scan = observer(() => {
  const navigation = useNavigation<NativeStackNavigationProp<AppStackParamList>>();
  const router = useRoute<RouteProp<AppStackParamList, '_Scan'>>();
  const routes = useNavigationState(state => state.routes);
  const prevRoute = routes[routes.length - 2];
  const [reader, setReader] = useState<DCVBarcodeReader>();
  // Ref
  const DCVRef = useRef<DCVCameraView>(null);
  const clear = async () => {
    console.log(router.params.key, 'clear start');
    if (reader) {
      reader.removeAllResultListeners();
      await reader.stopScanning();
      setReader(undefined);
    }
    console.log(router.params.key, 'clear end');
  };
  const createReader = async () => {
    console.log(router.params.key, 'createReader start');
    try {
      const _reader = await DCVBarcodeReader.createInstance();
      setReader(_reader);
    } catch (error: any) {
      console.log('create reader error =>', error);
    }
    console.log(router.params.key, 'createReader end');
  };
  const startScanning = async () => {
    console.log(router.params.key, 'const startScanning start');
    try {
      if (!reader) {
        console.log('no reader');
        return;
      }
      await reader.resetRuntimeSettings();
      // Set the expected barcode count to 0 when you are not sure how many barcodes you are scanning.
      // Set the expected barcode count to 1 can maximize the barcode decoding speed.
      let setting = await reader.getRuntimeSettings();
      setting.expectedBarcodesCount = 1;
      // Set the barcode format to read.
      setting.barcodeFormatIds = EnumBarcodeFormat.BF_ALL;
      // Apply the new runtime settings to the barcode reader.
      await reader.updateRuntimeSettings(setting);
      // await reader.startScanning();
    } catch (e) {
      console.log('startScanning');
      console.log(e);
    }
    console.log(router.params.key, 'const startScanning end');
  };
  const regularizeResults = (results: Array<BarcodeResult>) => {
    const resultList = results.map((result) => result.barcodeText);
    const resultBarcodes = removeUnExpectWord(resultList);
    return resultBarcodes;
  };
  const removeUnExpectWord = (barcodes: Array<string>) => {
    const reg = /[\{\}\[\]\/?.,;:|\)*~`!^\-_+<>@\#$%&\\\=\(\'\"]/gi;
    const editedBarcodes = barcodes.map((barcode) => {
      let _barcode = barcode.trim();
      if (_barcode.startsWith('{GS}')) {
        _barcode = _barcode.replace('{GS}', '');
      }
      if (_barcode.endsWith('{GS}')) {
        _barcode = _barcode.replace('{GS}', '');
      }
      return _barcode;
    });
    return editedBarcodes;
  };
  // 사진을 촬영하여 이미지 주소를 생성합니다.
  const handleMakePhotoUrl = async (fileUri: string) => {
    try {
      const file: IFile = {
        uri: fileUri,
        name: codeMasterStore.currentTime.toString(),
        type: 'image/jpeg',
      };
      const url = await imgUploadCall(file);
      return url;
    } catch (e) {
      console.log(e);
      console.log('handleMakePhotoUrl');
    }
  };
  // 화면을 캡쳐하고, 이와 별개로 바코드를 스캔합니다.
  const takeScreenShotAndBarcode = async () => {
    console.log(router.params.key, 'takeScreenShotAndBarcode start');
    try {
      if (!reader) {
        console.log('reader 없음');
        return;
      }
      const capture = await captureScreen({format: 'jpg', quality: 0.3});
      if (!capture) {
        return;
      }
      const url = await handleMakePhotoUrl(capture);
      reader.addResultListener(async (data) => {
        if (data) {
          const regularize = regularizeResults(data);
          if (regularize.length > 0) {
            reader.removeAllResultListeners();
            if (router.params.withImage) {
              await clear();
              // NOTE: react-native-view-shot
              // 해당 라이브러리는 ios 환경을 캡쳐할 수 없는 이슈
              if (Platform.OS === 'ios') {
                goPrevRoute(regularize[0].trim(), false);
              } else {
                goPrevRoute(regularize[0].trim(), true, url);
              }
            } else {
              await clear();
              goPrevRoute(regularize[0].trim(), false);
            }
          }
        }
      });
    } catch (e) {
      console.log('takeScreenShotAndBarcode');
      console.log(e);
    }
    console.log(router.params.key, 'takeScreenShotAndBarcode end');
  };
  const goPrevRoute = (barcode: string, withImage: boolean, imageUrl?: string | null) => {
    console.log(router.params.key, 'goPrevRoute start');
    if (!withImage) {
      // @ts-ignore
      navigation.navigate({
        name: prevRoute.name,
        params: {
          barcode: barcode,
          // @ts-ignore
          key: router.params.key,
        },
        merge: true,
      });
      console.log(router.params.key, 'goPrevRoute end');

      return;
    }
    // @ts-ignore
    navigation.navigate({
      name: prevRoute.name,
      params: {
        barcode: barcode,
        scannedImage: imageUrl,
        // @ts-ignore
        key: router.params.key,
      },
      merge: true,
    });
    console.log(router.params.key, 'goPrevRoute end');
  };
  useCustomHeader({
    title: '바코드 스캔',
  });
  useEffect(() => {
    if (reader) {
      // @ts-ignore
      console.log(router.params.key, 'Scan');
      console.log(reader, 'Scan');
      (async () => {
        try {
          loaderStore.setLoading(true);
          console.log(router.params.key, 'reader.startScanning start');
          await reader.startScanning();
          console.log(router.params.key, 'reader.startScanning end');
          loaderStore.setLoading(false);
        } catch (e) {
          console.log(e);
        }
      })();
    } else {
      (async () => {
        loaderStore.setLoading(true);
        await createReader();
        loaderStore.setLoading(false);
      })();
    }
  }, [reader]);
  return (
    <Layout collapsable={false}>
      <DCVCameraView
        style={{flex: 1, justifyContent: 'space-between'}}
        ref={DCVRef}
        overlayVisible={true}
        // @ts-ignore
        torchButton={{visible: true}}
        torchState={EnumTorchState.OFF}
        scanRegionVisible={true}
        scanRegion={ScanRegion}
      >
        <LicenseText>Powered by Dynamsoft</LicenseText>
        <NotificationView>
          <NotificationBox>
            <NotificationText>{`바코드를 사각형에 위치시키고\n바코드 스캔 버튼을 눌러주세요`}</NotificationText>
          </NotificationBox>
        </NotificationView>
        <BottomView>
          {loaderStore.loading ? null :
            <Button
              title="바코드 스캔"
              size="large"
              onPress={async () => {
                loaderStore.setLoading(true);
                await startScanning();
                await takeScreenShotAndBarcode();
                loaderStore.setLoading(false);
              }}
            />}
        </BottomView>
      </DCVCameraView>
    </Layout>
  );
});
export default Scan;

Unfortunately, this is not my code, so I don't know the reason for writing etc. in detail. So if there is a problem, I think it needs to be fixed. I would like to know the cause of Severe Hang additionally. Is there a logic that intentionally triggers Hang?

Dynamsoft-Henry commented 1 year ago

@badbeoti Thanks for sharing the code snippet. We will let you know if this issue is fixed.

badbeoti commented 1 year ago

스크린샷 2023-06-21 오후 2 15 58 스크린샷 2023-06-21 오후 2 16 16


As you can see occur severe hang when call getTimeOfDay. This timing is same that await this.reader.startScanning().

And I have some question.

  1. Should i create instance and addResultListener every single time to scan barcode?
  2. If the above is correct, Should i stopScanning and removeAllResultListeners after scan barcode every time?
  3. I tried to Profiling from xCode. And i found so many thread for using DynamsoftBarcode or DynamsoftCamera. Is it right? Please tell me if I'm using it wrong.

Currently experiencing inconvenience due to bugs. Anyway, I want you guys to solve it quickly.

@Dynamsoft-Henry

badbeoti commented 1 year ago

@Dynamsoft-Henry Can you check please.

Dynamsoft-Henry commented 1 year ago

Working on it now

Dynamsoft-Henry commented 1 year ago

Hi @badbeoti

For the 3 questions:

Question:

  1. Should i create instance and addResultListener every single time to scan barcode?
  2. If the above is correct, Should i stopScanning and removeAllResultListeners after scan barcode every time?

Answer:

What do you mean by "every single time" actually? You can addResultListener before the first-time barcode scanning. You don't need to always addResultListener or removeAllResultListeners during the video previewing. If you want to stop scanning the barcodes but not going to close the camera, please use the method stopScanning. When you have to trigger removeAllResultListeners is the time you want to leave the camera view page and close the camera.

Question:

  1. I tried to Profiling from xCode. And i found so many thread for using DynamsoftBarcode or DynamsoftCamera. Is it right? Please tell me if I'm using it wrong.

Answer:

"So many threads" is not a problem. At least, we also have that many threads when running our sample. This is not the reason for the hang.

Based on the code snippet, there are some suggestions from our developer.

We suggest you to make some modifications on the "useEffect":

useEffect(() => { if (reader) { // @ts-ignore console.log(router.params.key, 'Scan'); console.log(reader, 'Scan'); (async () => { try { loaderStore.setLoading(true); console.log(router.params.key, 'reader.startScanning start'); await reader.startScanning(); console.log(router.params.key, 'reader.startScanning end'); loaderStore.setLoading(false); // move you code of addResultListener from takeScreenShotAndBarcode to this place. reader.addResultListener(async (data) => { if (data) { const regularize = regularizeResults(data); if (regularize.length > 0) { // remove the removeAllResultListener here. // reader.removeAllResultListeners(); if (router.params.withImage) { // remove the clear() here // await clear(); // NOTE: react-native-view-shot // 해당 라이브러리는 ios 환경을 캡쳐할 수 없는 이슈 if (Platform.OS === 'ios') { goPrevRoute(regularize[0].trim(), false); } else { goPrevRoute(regularize[0].trim(), true, url); } } else { await reader.stopScanning(); // remove the clear() here //await clear(); goPrevRoute(regularize[0].trim(), false); } } } }); } catch (e) { console.log(e); } })(); // } else { (async () => { loaderStore.setLoading(true); await createReader(); loaderStore.setLoading(false); })(); } // Add the following code return () => { await clear(); } }, []);//Leave the second parameter of useEffect empty

We can't run your code snippet because it seems to be not completed and includes some third-party components. We are not sure whether the above suggestions can solve your issue.

Dynamsoft-Henry commented 1 year ago

The following question we can't understand. Are there any relations between getTimeOfDay and our startScanning?

As you can see occur severe hang when call getTimeOfDay. This timing is same that await this.reader.startScanning().

badbeoti commented 1 year ago

image

First, We fixed the problem by modifying the DYSCameraView.m Can you check this code got no problem?

And "What it's mean every single time". You need to know multiple scan page we have. We use many Scan page each has unique key value(and it is new created every single time). So, User can visit different Scan page. and it can happen in a very short time.

Test attempted without addResultListener and removeAllResultListeners. It had only left DCVCameraView.

const Scan = observer(() => {
  return (
    <Layout collapsable={false}>
      <DCVCameraView
        style={{flex: 1, justifyContent: 'space-between'}}
        ref={DCVRef}
        overlayVisible={true}
        // @ts-ignore
        torchButton={{visible: true}}
        torchState={EnumTorchState.OFF}
        scanRegionVisible={true}
        scanRegion={ScanRegion}
      >
      </DCVCameraView>
    </Layout>
  );
});
export default Scan;

And we got same result(severe hang).

We also tried a different code. Similar to what you suggested like clearFn inside useEffect() But we got same result again.

image

We checked the error log more accurately using Sentry. We can found __psynch_mutexwait when we got severe hang. I think it's not problem in react-native cause it happen only iOS. I'm not iOS developer. I don't know exactly but you guys something seems to have set the mutex wrong. If not maybe we used this wrong.

if ([StaticClass instance].dce == nil) {
            [StaticClass instance].dce = [[DynamsoftCameraEnhancer alloc] init];
        }

Anyway we solve problem use this. [StaticClass instance].dce = [[DynamsoftCameraEnhancer alloc] init] only one time.

If this way is wrong choice. Please leave a comment. Thx.

@Dynamsoft-Henry

Dynamsoft-Henry commented 1 year ago

@badbeoti

Your change is correct.

tousif-p99soft commented 11 months ago

@Dynamsoft-Henry Any release plan for this fix?

tousif-p99soft commented 11 months ago

@badbeoti If possible, can you please create a PR for this fix?

Dynamsoft-Henry commented 11 months ago

Hi @tousif-p99soft

Actually which version are you working on? Our latest version is v1.1.12. Would you like to try the latest version? Our developers said they have fixed this issue in the v1.1.12 version.

"dynamsoft-capture-vision-react-native": "^1.1.12",

tousif-p99soft commented 11 months ago

Hi,

I am using the same version but that code change is not there

On Thu, 7 Sept 2023 at 11:23, Dynamsoft-Henry @.***> wrote:

Hi @tousif-p99soft https://github.com/tousif-p99soft

Actually which version are you working on? Our latest version is v1.1.12. Would you like to try the latest version? Our developers said they have fixed this issue in the v1.1.12 version.

"dynamsoft-capture-vision-react-native": "^1.1.12",

— Reply to this email directly, view it on GitHub https://github.com/Dynamsoft/capture-vision-react-native/issues/41#issuecomment-1709518171, or unsubscribe https://github.com/notifications/unsubscribe-auth/A4TTLCIKYRCO3ARANU3475TXZFOL5ANCNFSM6AAAAAAZISW3OM . You are receiving this because you were mentioned.Message ID: @.***>

Dynamsoft-Henry commented 11 months ago

@tousif-p99soft Thanks, we will check it again.

Dynamsoft-Henry commented 11 months ago

@tousif-p99soft Would you like to send us your simplified project or code snippet to show us how you are using the SDK.

Since we can't reproduce the frozen. We need your code snippet to make more tests so that we can confirm the bugs are fixed before release.

Dynamsoft-Henry commented 11 months ago

@tousif-p99soft If possible, would you like to share your package.json file with us?