Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server is Crashing on Android - React Native Expo App #26

Open
jyotipixolo opened this issue Mar 21, 2023 · 15 comments
Open

Server is Crashing on Android - React Native Expo App #26

jyotipixolo opened this issue Mar 21, 2023 · 15 comments
Labels
Expo Expo - Isn't officially supported yet. On Hold Cannot be resolved at the moment. P1 High priority issue.

Comments

@jyotipixolo
Copy link

jyotipixolo commented Mar 21, 2023

The plugin is working fine on IOS. But the server is getting crashed on Android after some time.

Package JSON

{
  "name": "otg-olp-mobile-app",
  "version": "1.0.0",
  "scripts": {
    "start": "expo start --dev-client",
    "android": "expo run:android",
    "ios": "expo run:ios",
    "web": "expo start --web"
  },
  "dependencies": {
    "@dr.pogodin/react-native-static-server": "^0.7.2",
    "@react-native-async-storage/async-storage": "~1.17.3",
    "@react-native-community/netinfo": "^9.3.5",
    "@react-navigation/native": "^6.1.2",
    "@react-navigation/stack": "^6.3.11",
    "axios": "^1.3.3",
    "expo": "~47.0.12",
    "expo-file-system": "^15.2.2",
    "expo-font": "~11.0.1",
    "expo-progress": "^0.0.2",
    "expo-screen-orientation": "~5.0.1",
    "expo-splash-screen": "~0.17.5",
    "expo-sqlite": "^11.0.0",
    "expo-status-bar": "~1.4.2",
    "react": "18.1.0",
    "react-dom": "18.1.0",
    "react-native": "0.70.5",
    "react-native-fs": "^2.20.0",
    "react-native-gesture-handler": "~2.8.0",
    "react-native-keyboard-aware-scroll-view": "^0.9.5",
    "react-native-paper": "^5.2.0",
    "react-native-safe-area-context": "4.4.1",
    "react-native-screens": "~3.18.0",
    "react-native-svg": "13.4.0",
    "react-native-svg-transformer": "^1.0.0",
    "react-native-web": "~0.18.9",
    "react-native-webview": "11.23.1",
    "react-native-xml2js": "^1.0.3",
    "react-native-zip-archive": "^6.0.9"
  },
  "devDependencies": {
    "@babel/core": "^7.12.9",
    "@types/react": "^18.0.28",
    "@types/react-native": "^0.71.3",
    "typescript": "^4.6.3"
  },
  "private": true
}

The way my app works is. I download a zip folder from a remote URL, I then extract it to a folder in RNFS /scorm-player/ 
Similarly, I download multiple modules to the same directory. Each module being anywhere between 20 MB - 160 MB in size. All of this happens before I start the local server. When I open the page where my Local Server is needed. I'm using this code to run the server. 


// Function to check if the server is active or not and create a new one if not
	const checkAndCreateServer = () => {
		// Get the active server
		let server = getActiveServer();
		if (!server) {
			// If no server is active, create a new one
			server = new Server({
				fileDir: RNFS.DocumentDirectoryPath + "/scorm-player",
				stopInBackground: true,
			});
		}
		// If the server is inactive, start it
		if (server) {
			if (server.state === STATES.INACTIVE) {
				server.start().then((url) => {
					console.log("Server started at: ", url);
					setLocalHostURI(url);
				});
			} else {
				setLocalHostURI(server.origin);
			}
		}
	};


Before I can update the SRC in the webview, I need to get some data (from a local SQLite database) about the module I want to play in the Webview, and create GET REQUEST PARAM string to pass in the Source URL. This works perfectly fine when I'm testing it on a iOS Simulator. But when I run the same thing on an Android Simulator. It works perfectly the first time. After that when I go back and open the page again, the app freezes completely, without throwing any exception. And then I see a console in Android Studio which says "Server crashed"

WhatsApp Image 2023-03-18 at 19 05 22

@birdofpreyru
Copy link
Owner

Hi @jyotipixolo , I think you might be doing it wrong: with stopInBackground: true the server will automatically stop when the app goes into background (and it will make its state INACTIVE), and then it will automatically re-start when the app is in the foreground again. The check if (server.state === STATES.INACTIVE) { in such scenario still may evaluate true, and thus trigger .start() once again, causing the crash.

As a rapid test, can you try adding server.stop() call just before server.start()? For server in INACTIVE state it will ensure no automatic re-start will happen until the .start() is explicitly called. If that does not solve the issue, then there is some bug to fix in the library, otherwise I'd say your code around the server should be refactored.

@jyotipixolo
Copy link
Author

jyotipixolo commented Mar 22, 2023

Hi @birdofpreyru, I tried stopping the server before starting it. To make things easier I took out all of my other code regarding the data and now I am just starting(After stopping) the server when I land on the screen and stopping when I exit.


        // Function to check if the server is active or not and create a new one if not
	const checkAndCreateServer = async () => {
		// Get the active server - get the running server
		let server = getActiveServer();
		if (!server) {
			// If no server is active, create a new one
			server = new Server({
				fileDir: RNFS.DocumentDirectoryPath + "/scorm-player",
				nonLocal: true,
			});
		}
		if (server) {
			await server.stop();
			server.start().then((url) => {
				console.log("Server started at: ", url);
				setLocalHostURI(url);
			});
		}
	};
           useEffect(() => {
		    checkAndCreateServer();
	    }, []);
        const backAction = async () => {
		// Stop the server
		const server = getActiveServer();
		if (server && server.state === STATES.ACTIVE) {
		      await  server.stop();
		}
		navigation.goBack();
	};

But my server still crashes. And the crash happens at random moments, sometimes on server start and sometimes on server stop.
Most of the times the server starts successfully the first time but causes issues when I go back from the screen and return to it.

Could this be a memory issue in android ?
Is there a better way to handle the stopping of the server?

I don't always get the Server Crashed exception in logcat but the app still freezes.
Although, when comment the server code my app runs smoothly.

@birdofpreyru
Copy link
Owner

Agh... you know, getActiveServer() only returns a server, if any, when it is non-INACTIVE, and non-CRASHED. Doing what you doing, I guess, you are leaking some created server instances because they were INACTIVE when you call getActiveServer(), and attempting to create and start new server instances you eventually crash the app.

You should do at least as in the example app, where the hook just keeps server reference and stops it on unmount, i.e. (over-simplifying the example):

useEffect(() => {
  const server = new Server(..);
  ...
  server.start(..).then(..);
  return () => {
    server.stop();
  };
}, []);

this way you don't leak references to the server instances you create, and ensure that every server is correctly shutdown when the hook unmounts. Note, even with such pattern, the example does it in the root app component, thus it is sure the hook never executed more than once a time in the app (in contrast to if you put it into some component, which can be mounted and unmounted). If you do it in some component, probably getActiveServer() is not enough to synchronize operation of multiple server instances... although I added it into lib, I haven't gave it too much thought, as in my projects I always rely on a single server instance in the root of the app, like in the example.

@birdofpreyru birdofpreyru added the On Hold Cannot be resolved at the moment. label Mar 22, 2023
@ZakiPathan2010
Copy link

I also got the same issue. I have expo bare workflow project.
What I already try till now:

  1. Stop the server when screen closed
  2. Wait until server is stop with then & catch block. In then block I do navigation.goBack()
  3. Never stop the server
  4. Check if any active server is there if not then only create & start a new server
  5. Stop previous server before starting a new one

Nothing works. App is freezing randomly on different points. & without server code everything is working fine.

I am attaching one simple project with server code with 2 screens in it, just starting & stopping the server in different ways. You can check it here : https://we.tl/t-dZ1nq3dmQk

@birdofpreyru
Copy link
Owner

@ZakiPathan2010 please, re-read my previous comment. I see in your giant archive the same code fragment I commented about above, which potentially creates multiple server instances when the screen mounts/unmounts, because there is no syncrhonization between different screen instances, I guess; also it does not stop the server when screen is unmounted — I don't know what are you trying to do, but probably you'll do better if you just create a single server instance at the root of the app, and stop it when you are sure you don't need it.

@jyotipixolo
Copy link
Author

One more thing that needs to be considered is that my code is working absolutely fine in the IOS simulator and on a real device. But the server is crashing on Android.

@birdofpreyru
Copy link
Owner

Well, on Android I use it in several app distributed via Google.Play, and installed on ~5k devices in total. All set up to report me errors and crashes via Sentry. So far I haven't seen the servers crashing there. Maybe I miss something; maybe your code works fine in ios and simulator because they implement the execution flow sync between js and native differently and it prevents potential issues with your code i pointed above.

I'll be on the lookout for crashes on Android, but so far haven't seen them myself, but I also use it the way demoed in the example app.

@jyotipixolo
Copy link
Author

I tried clearing all the server instances before I start a new server. I made a function that returned an array of all the servers (in any state they are in). This always returned an empty array, only then I started a new server. But the freezing still exists. The server crash happens inside the "launch" function in the server.java file. Because any log after that is not seen. I also put the start server code inside a useEffect in my app.tsx too, with a plan that I start the server initially and keep it that way till I need it in my inner page. But I don't even get past my Login Screen and the server crashes and the app freezes. Is these a version of the plugin that is more suited for Android?

@birdofpreyru
Copy link
Owner

The server crash happens inside the "launch" function in the server.java file.

That launch() function is the JNI entry-point into the underlying C-code of Lighttpd server.

any log after that is not seen

You actually can see logs from Lighttpd in adb logcat outputs; and if you uncomment these lines in the Lighttpd config the library generates internally, you even can direct all log output from Lighttpd core into a dedicated file, that later can be found and inspected with adb.

I don't even get past my Login Screen and the server crashes and the app freezes

I believe, earlier you told "Most of the times the server starts successfully the first time but causes issues when I go back from the screen and return to it", which to me indicates that you do some mistakes in handling those restarts.

Is these a version of the plugin that is more suited for Android?

No, there is no special version for Android, I rely on the same latest versions I released on NPM.

@jyotipixolo
Copy link
Author

jyotipixolo commented Mar 23, 2023

I'm just trying to make it work now. So this is what I'm doing in my App.tsx. I tried to replicate the example code. I have taken out any restart code, or any references to the plugin on any other components.

The server starts normally like always.

My App.tsx has a stack of screens, the initial one being the Login Screen. While I'm still on the Login Screen the server crashes.

useEffect(() => {
  let server: null | Server = new Server({
    fileDir: RNFS.DocumentDirectoryPath + "/scorm-player",
    stopInBackground: true,
  });
  (async () => {
    const res = await server?.start();
    if (res && server) {
      console.log("Server is running");
    }
  })();
  return () => {
    (async () => {
      server?.stop();
      server = null;
    })();
  };
}, []);

These are the Logs I feel would be relevant.

**lighttpd** - E - (../../../../../lighttpd1.4/src/server.c.1057) [note] graceful shutdown started **lighttpd** - E - (../../../../../lighttpd1.4/src/server.c.2082) server stopped by UID = 10716 PID = 6606 **RN_STATIC_SERVER** - I - Res -1 **RN_STATIC_SERVER** - E - Server crashed java.lang.Exception: Native server exited with status -1 at com.lighttpd.Server.run(Server.java:82)

The above logs were formed without server.stop() being called.

I noticed another log, way before the crash happened.
**ReactNativeJNI** - I - Memory warning (pressure level: TRIM_MEMORY_RUNNING_CRITICAL) received by JS VM, running a GC
But again, when I'm not starting the server, the freezing issue stops.

Do you think there are any other packages in my code that might be clashing with yours?

@birdofpreyru
Copy link
Owner

Well... are you sure there is nothing else in your app consuming too much memory, and thus triggering OS to garbage-collect / kill stuff?

ReactNativeJNI - I - Memory warning (pressure level: TRIM_MEMORY_RUNNING_CRITICAL) received by JS VM, running a GC

☝️ This reads like, yeah, too much memory consumed, OS started to fight for resources.

lighttpd - E - (../../../../../lighttpd1.4/src/server.c.1057) [note] graceful shutdown started
lighttpd - E - (../../../../../lighttpd1.4/src/server.c.2082) server stopped by UID = 10716 PID = 6606

☝️ These actually come from Lighttpd core. Should be looked up there, but the way they read... looks like the server attempted and completed a graceful shutdown, the same like when .stop() is called. If you haven't asked for it, neither app transition to background, alongside with stopInBackground flag caused it... I believe Lighttpd listen for OS interrupts, I guess if Android runs out of memory, and sends some interrupts to app processes asking them to quit, if possible, to reclaim some memory, probably that can trigger server's graceful shutdown and exit with non-zero status code — this all should be checked, I am just saying out of my head, not really knowing this stuff in details.

RN_STATIC_SERVER - I - Res -1 RN_STATIC_SERVER - E - Server crashed java.lang.Exception: Native server exited with status -1 at com.lighttpd.Server.run(Server.java:82)

☝️ And this comes from Java layer of this library, specifically from this point. As you see, there is a trivial logic behind it — if process exited with non-zero status — throw exception, say it crashed.

So, if you wanna look into this further yourself, I guess the next thing is to look into Lighttpd sources, this file specifically, and try to figure out what can cause it to attempt a graceful shutdown and exit with -1 status.

@ZakiPathan2010
Copy link

ZakiPathan2010 commented Mar 24, 2023

Hi @birdofpreyru , so here is what I’m trying to do in my sample project. This project has nothing else to make sure there is no memory leak from anywhere else. I started the server in app.tsx file (just like in the example code) which is the entry point of my app & I have two screens which navigates to each other without any server code. There is no reference to the server anywhere else in the code except the App.tsx. So what happens here, is that my server is starts as usual & when I navigate from 1st screen to 2nd screen & go back to 1st screen, after a few times the app freezes. In this process the app is not in background & I never closed it.

In my main app too, after starting the server I would have to navigate to other screens as I did in my last example.

I am attaching my project here so you can try it by yourself too. Sample Project
Also things to note are: my project is a expo ejected bare React project.

@birdofpreyru birdofpreyru added P1 High priority issue. On Hold Cannot be resolved at the moment. and removed On Hold Cannot be resolved at the moment. labels Mar 28, 2023
@birdofpreyru
Copy link
Owner

So, I arrived to briefly look at your sample project now; and only now I realised it is an Expo project, which this library does not yet support officially. I tried to build by first removing artifacts from your builds you packed into the archive, and then doing npm install; npm run android — that breaks for me somewhere in the middle of the build. I guess, it requires some extra Expo-specific actions.

Thus, I guess this issue will be on hold until I decide to support Expo. Before that, I can just re-iterate, for me the server works fine on Android with a regular RN project (see the example app in the repo); and stay on look-out for possible Android-specific issues.

@birdofpreyru birdofpreyru added the Expo Expo - Isn't officially supported yet. label May 9, 2023
@birdofpreyru
Copy link
Owner

birdofpreyru commented Jun 10, 2024

Usage in React Native: ...

Tells who? Some kind of Artificial Idiocy?

NanoHttpd: Commonly used in React Native, particularly for serving local files within the application. This is because it can be easily embedded and runs efficiently within the Android environment.

  1. HanoHttpd was a Java-only server; and React Native is at least Android, iOS, macOS, Windows.
  2. The last release of NanoHttpd was 8 years ago, with no further development seen in their repo.

These are two main reasons why this library is powered by Lighttpd — the same, actively maintained server across all platforms.

That's why our server was getting killed and it was crashing.

Nah... most probably it crashes because you do something wrong in your code; or there might be some bug in the library somewhere.

@gstrauss
Copy link

@jyotipixolo

That's why our server was getting killed and it was crashing.

Maybe I missed it, but you provided no evidence for that statement. Just an unsubstantiated conclusion.

If you can trace the process or thread which starts lighttpd using strace, that may help identify what system call is failing and resulting in lighttpd shutting down. (I don't know if this is possible in the Android debug env)

[note] graceful shutdown started suggests that the lighttpd thread received SIGINT (lighttpd graceful shutdown) or SIGUSR1 (lighttpd graceful restart). If that was not intended, then maybe your app should block signals in the lighttpd thread so that signals are not delivered to the lighttpd thread, e.g. sigprocmask(). That might be a good idea if the java app manages the lifetime of the lighttpd thread and you do not want/need lighttpd to react to signals.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Expo Expo - Isn't officially supported yet. On Hold Cannot be resolved at the moment. P1 High priority issue.
Projects
None yet
Development

No branches or pull requests

4 participants