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

Crash when using Java interface on Windows #690

Closed
robnugent opened this issue Apr 25, 2023 · 5 comments · Fixed by #697
Closed

Crash when using Java interface on Windows #690

robnugent opened this issue Apr 25, 2023 · 5 comments · Fixed by #697

Comments

@robnugent
Copy link

robnugent commented Apr 25, 2023

Hello,

First off, many thanks for providing toktx and the Java interface. I'm using this to compress about 12,000 GIS related textures to ASTC format, but I'm seeing a persistent crash in KtxTexture2.destroy().

I've done my best to reduce this to a simple testcase. Here it is:

package uk.co.abraded.toktx;

import org.khronos.ktx.KtxAstcParams;
import org.khronos.ktx.KtxCreateStorage;
import org.khronos.ktx.KtxErrorCode;
import org.khronos.ktx.KtxPackAstcBlockDimension;
import org.khronos.ktx.KtxPackAstcEncoderMode;
import org.khronos.ktx.KtxPackAstcQualityLevel;
import org.khronos.ktx.KtxTexture2;
import org.khronos.ktx.KtxTextureCreateInfo;
import org.khronos.ktx.VkFormat;

public class ToKTXBug {

   private static boolean nativeLoaded = false;

   public static void main(String[] args) {
      System.out.println("Main() >");

      loadNative();

      // Repeatedly create a compress an image. On my machine this segfaults
      // after about 500 iterations
      for (int i = 0; i < 10000; i++) {
         final int size = convertToASTC();
         System.out.println("Iteration: " + i + ", compressed data size is " + size);
      }

      System.out.println("Main() <");
   }

   private synchronized static void loadNative() {
      if (!nativeLoaded) {
         System.loadLibrary("ktx-jni");
         nativeLoaded = true;
      }
   }

   public static int convertToASTC() {
      loadNative();

      final int w = 1024;
      final int h = 1024;

      // Create Uncompressed texture
      final KtxTextureCreateInfo info = new KtxTextureCreateInfo();
      info.setBaseWidth(w);
      info.setBaseHeight(h);
      info.setVkFormat(VkFormat.VK_FORMAT_R8G8B8_SRGB); // Uncompressed
      final KtxTexture2 t = KtxTexture2.create(info, KtxCreateStorage.ALLOC);

      // Pass the uncompressed data
      int bufferSize = w * h * 3;
      final byte[] rgbBA = new byte[bufferSize];
      t.setImageFromMemory(0, 0, 0, rgbBA);

      // Compress the data
      final KtxAstcParams p = new KtxAstcParams();
      p.setBlockDimension(KtxPackAstcBlockDimension.D8x8);
      p.setMode(KtxPackAstcEncoderMode.LDR);
      p.setQualityLevel(KtxPackAstcQualityLevel.EXHAUSTIVE);
      final int rc = t.compressAstcEx(p);
      if (rc != KtxErrorCode.SUCCESS) {
         throw new RuntimeException("ASTC error " + rc);
      }
      final int retDataLen = (int) t.getDataSize();
      
      // Free things up - segfault usually occurs inside this destroy() call
      t.destroy();
      
      return retDataLen;
   }
}

On my Windows x64 box, this reliably crashes after about 400-500 iterations:

Iteration: 437, compressed data size is 262144
Iteration: 438, compressed data size is 262144
Iteration: 439, compressed data size is 262144
Iteration: 440, compressed data size is 262144
Iteration: 441, compressed data size is 262144
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00007ffc9557403d, pid=2008, tid=3568
#
# JRE version: Java(TM) SE Runtime Environment (17.0.1+12) (build 17.0.1+12-LTS-39)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (17.0.1+12-LTS-39, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, windows-amd64)
# Problematic frame:
# V  [jvm.dll+0x3e403d]
#
# No core dump will be written. Minidumps are not enabled by default on client versions of Windows
#
# An error report file with more information is saved as:
# R:\svn\abraded\rob\code\trunk\src\modules\hs_err_pid2008.log
#
# If you would like to submit a bug report, please visit:
#   https://bugreport.java.com/bugreport/crash.jsp
#

The stack trace in the hotspot dump file is:

Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
J 738  org.khronos.ktx.KtxTexture.destroy()V libktx@4.1.0 (0 bytes) @ 0x00000260ac920fe3 [0x00000260ac920fa0+0x0000000000000043]
J 761 c1 uk.co.abraded.toktx.ToKTXBug.convertToASTC()I uk.co.abraded.toktx (136 bytes) @ 0x00000260a55b8e1c [0x00000260a55b85a0+0x000000000000087c]
j  uk.co.abraded.toktx.ToKTXBug.main([Ljava/lang/String;)V+20 uk.co.abraded.toktx
v  ~StubRoutines::call_stub

I managed to put some printfs in the native code, and it seems to be crashing inside the call to ReleaseByteArrayElements in KtxTexture.free_buffer_list(). The argment passed to ReleaseByteArrayElements look good and match those from the corresponding getByteArrayElements() so I suspect storage is getting trampled during execution of the encoder.

Two other comments:

  1. My real code also fails in this way on Windows/ARM64, but I can't get this reduced testcase to fail.
  2. Commenting out the call to destroy in this testcase avoids the issue, but the memory leakage means that I can't process all my 12,000 textures ina single run.

Many thanks in advance if you are able to look into this.
Regards,
Rob

@MarkCallow
Copy link
Collaborator

Thanks for the report. @ShukantPal please look into this?

@ShukantPal
Copy link
Contributor

The argment passed to ReleaseByteArrayElements look good and match those from the corresponding getByteArrayElements() so I suspect storage is getting trampled during execution of the encoder.

By trampled, do you mean libktx is corrupting the original byte array? This would seem like a bug in the encoder, but I can try debugging when I have time @MarkCallow.

@MarkCallow
Copy link
Collaborator

By trampled, do you mean libktx is corrupting the original byte array?

I assume that's what @robnugent is saying.

This would seem like a bug in the encoder, but I can try debugging when I have time @MarkCallow.

Yes it would but we have many native tests of the ASTC encoder and we don't see these crashes. Mind you we aren't doing 500 iterations. The person most familiar with the ASTC encoder is on vacation until the start of May and since the test case is Java, please try debugging when you have time.

@robnugent
Copy link
Author

robnugent commented Apr 26, 2023

By trampled, do you mean libktx is corrupting the original byte array?

I assume that's what @robnugent is saying.

Mark - yes - apologies for not being clear - that's what I meant - or more accurately, it's probably the JVM's control structures pertaining to the byte array that have been corrupted.

I put a dignostic message in Java_org_khronos_ktx_KtxTexture_setImageFromMemory() thus:

    ktx_uint8_t *src = reinterpret_cast<ktx_uint8_t*>(env->GetByteArrayElements(srcArray, NULL));
    std::cout << "RFN: setImageFromMemory() : got " << static_cast<void*>(srcArray) << ":" << static_cast<void*>(src) << std::endl;

and in free_buffer_list() thus:

        std::cout << "RFN: free_buffer_list() : " << static_cast<void*>(buffer.handle) << ":" << static_cast<void*>(buffer.data) << std::endl;
        env->ReleaseByteArrayElements(buffer.handle, buffer.data, JNI_ABORT);

Which gives me the following two lines of output before the crash inside ReleaseByteArrayElements():

RFN: setImageFromMemory() : got 000000A6B95F8F80:00000285A1F10000
RFN: free_buffer_list() : 000000A6B95F8F80:00000285A1F10000

So valid values are being given to the ReleaseByteArrayElements call, i.e. the same ones obtained from the prior GetByteArrayElements() call, which should have no reason to fail.

I hope this helps - many thanks for looking into this, and thanks again for providing this software in the first place.
Regards,
Rob

@robnugent
Copy link
Author

In case it helps, here's a tweak of my original testcase that uses multiple threads and random texture sizes. This crashes quicker for me, even if the number of threads is set to 1, including in Windows/ARM64 where my previous test did not reliably fail.

package uk.co.abraded.toktx;

import java.util.Random;
import org.khronos.ktx.KtxAstcParams;
import org.khronos.ktx.KtxCreateStorage;
import org.khronos.ktx.KtxErrorCode;
import org.khronos.ktx.KtxPackAstcBlockDimension;
import org.khronos.ktx.KtxPackAstcEncoderMode;
import org.khronos.ktx.KtxPackAstcQualityLevel;
import org.khronos.ktx.KtxTexture2;
import org.khronos.ktx.KtxTextureCreateInfo;
import org.khronos.ktx.VkFormat;

/**
 *
 * @author rob
 */
public class ToKTXBug implements Runnable {

    private static boolean nativeLoaded = false;
    private static final int NUM_THREADS = 15;

    private int tid;
    private final Random r = new Random();

    public static void main(String[] args) {
        System.out.println("Main() >");

        loadNative();

        for (int i = 0; i < NUM_THREADS; i++) {
            final ToKTXBug b = new ToKTXBug(i);
            final Thread t = new Thread(b);
            t.setDaemon(false);
            t.start();
        }

        System.out.println("Main() <");
    }

    public ToKTXBug(int tid) {
        this.tid = tid;
    }

    public void run() {
        System.out.println(tid + " run() >");
        // Repeatedly create a compress an image.
        for (int i = 0; i < 10000; i++) {
            final int w = (r.nextInt() % 512) + 1024;
            final int h = w;
            final int size = convertToASTC(w, h);
            System.out.println(tid + " iteration: " + i + ", size: " + w + "x" + h +  ", compressed data size is " + size);
        }
        System.out.println(tid + " run() < ");
    }

    private synchronized static void loadNative() {
        if (!nativeLoaded) {
            System.loadLibrary("ktx-jni");
            nativeLoaded = true;
        }
    }

    public int convertToASTC(int w, int h) {
        loadNative();

        // Create Uncompressed texture
        final KtxTextureCreateInfo info = new KtxTextureCreateInfo();
        info.setBaseWidth(w);
        info.setBaseHeight(h);
        info.setVkFormat(VkFormat.VK_FORMAT_R8G8B8_SRGB); // Uncompressed
        final KtxTexture2 t = KtxTexture2.create(info, KtxCreateStorage.ALLOC);

        // Pass the uncompressed data
        int bufferSize = w * h * 3;
        final byte[] rgbBA = new byte[bufferSize];
        t.setImageFromMemory(0, 0, 0, rgbBA);

        // Compress the data
        final KtxAstcParams p = new KtxAstcParams();
        p.setBlockDimension(KtxPackAstcBlockDimension.D8x8);
        p.setMode(KtxPackAstcEncoderMode.LDR);
        p.setQualityLevel(KtxPackAstcQualityLevel.EXHAUSTIVE);
        final int rc = t.compressAstcEx(p);
        if (rc != KtxErrorCode.SUCCESS) {
            throw new RuntimeException("ASTC error " + rc);
        }
        final int retDataLen = (int) t.getDataSize();

        // Free things up - segfault usually occurs inside this destroy() call
        t.destroy();

        return retDataLen;
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants