-
-
Notifications
You must be signed in to change notification settings - Fork 31
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
File descriptors not being released when File IO tracing feature is enabled #375
Comments
Hello @ntoskrnl thanks for reporting. We'll take a look. I assume you have already disabled file instrumentation for now, correct? |
@ntoskrnl can you please confirm some assumptions:
I am able to reproduce the crash but I can't see the problem using |
Oh and can you please also share the code you're using to reproduce just so we can verify it also fixes your problem once we come up with a solution. |
To test it on real device with ADB, it should be rooted. I am testing on both emulator and phone, and run lsof (without arguments) from within the app (using Here are some helper classes to reproduce the issue:
object Shell {
private const val TAG = "Shell"
fun shellOutput(command: String?): String {
var process: Process? = null
var bufferedReader: BufferedReader? = null
return try {
process = Runtime.getRuntime().exec(command)
bufferedReader = BufferedReader(
InputStreamReader(process.inputStream)
)
val log = StringBuilder()
var line: String?
log.append(String.format("Executing a command '%s'", command)).append("\n")
while (bufferedReader.readLine().also { line = it } != null) {
log.append(line).append("\n")
}
log.toString()
} catch (e: IOException) {
Log.e(TAG, "logcat extraction error", e)
""
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close()
} catch (e: IOException) {
Log.e(TAG, "logcat reading error", e)
}
}
process?.destroy()
}
}
}
class MainActivity : AppCompatActivity() {
private var job: Job? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onStart() {
super.onStart()
job = GlobalScope.launch(Dispatchers.IO) {
launch {
fileWriterTest()
}
launch {
fdMonitor()
}
}
}
override fun onStop() {
job?.cancel()
job = null
super.onStop()
}
private suspend fun fileWriterTest() {
val cacheDir = cacheDir
while (true) {
runInterruptible {
val file = File(cacheDir, "${FILE_NAME_PREFIX}${System.currentTimeMillis()}.bin")
FileOutputStream(file, false).use { outputStream ->
outputStream.write("Hello world".toByteArray())
outputStream.flush()
}
Thread.sleep(499)
file.delete()
}
delay(1)
}
}
private suspend fun fdMonitor() {
while (true) {
val allLines = runInterruptible { Shell.shellOutput("lsof").lines() }
val fdCount = allLines.size
val deletedCount = allLines.count { "(deleted)" in it }
val testFileCount = allLines.count { FILE_NAME_PREFIX in it }
val testDeletedCount = allLines.count { FILE_NAME_PREFIX in it && "(deleted)" in it }
Timber.tag("FdMonitor").v("%d total files (deleted %d), %d test files (deleted %d)", fdCount, deletedCount, testFileCount, testDeletedCount)
delay(5000)
}
}
companion object {
private const val FILE_NAME_PREFIX = "test-file-"
}
} Here is the output that I get (filter by "FdMonitor"):
To reproduce crash faster, I also reduce the max open file limit via native call to |
It's actually not issue of Gradle plugin (the plugin and byte-code transformation works). But the problem with |
The problem is simple: when SentryFileOutputStream it opens 2 file descriptors instead of 1. When we call constuctor: public SentryFileOutputStream(final @Nullable File file, final boolean append)
throws FileNotFoundException {
this(init(file, append, null, HubAdapter.getInstance()));
} the delegate = new FileOutputStream(file); This is file descriptor 1. Then we call private constructor private SentryFileOutputStream(final @NotNull FileOutputStreamInitData data)
throws FileNotFoundException {
super(data.file, data.append);
spanManager = new FileIOSpanManager(data.span, data.file, data.isSendDefaultPii);
delegate = data.delegate;
} which calls Thus, there will be 2 FDs: One in the delegate and another inside Closing Maybe here in the private constructor it should call |
Thanks @ntoskrnl for the PR and the infos here, will do some testing on your changes today then hopefully merge and release soon. I've come up with a different approach which I wanted to improve before releasing but I like yours better, so going with that instead. |
Version |
Gradle Version
7.3.3
AGP Version
7.1.0
Code Minifier/Optimizer
No response
Version
3.1.5
Sentry SDK Version
6.4.1
Steps to Reproduce
Apply Gradle plugin to project with default configuration.
In a loop, create a file, write to file, close file and delete it.
Run
lsof
command and check if file descriptors are released.Expected Result
My expectation is that file descriptors should be released if the app doesn't use corresponding files and closes them correctly.
Tracing File IO feature causes file descriptors leak and eventually crashes the app. If this is expected behaviour, this should be documented.
Actual Result
File descriptors are still shown in the output of
losof
. If app creates files, after a while, it crashes with various errors, but the root cause is "To many open files".The text was updated successfully, but these errors were encountered: