[Javawatch Live] La historia de una solicitud de extracción. `os.version` en SubstrateVM

Ha pasado un año desde que el truco anterior fue exitoso: publicar un video en YouTube en lugar de una publicación. "Charla vergonzosa sobre singleton" obtuvo 7k visitas en YouTube y el doble en Habré en la versión de texto. Para un artículo escrito en un estado completamente terco y que habla sobre el antiguo acordeón de botones, esto es un poco exitoso.

Hoy he estado instalando un nuevo lanzamiento toda la noche. Esta vez, el tema es mucho más reciente: la historia del compromiso con la tecnología experimental: SubstrateVM. Pero el grado de tenacidad ha aumentado a un nuevo nivel.



Realmente espero sus comentarios! Te recuerdo que si realmente quieres mejorar algo en esta publicación, es mejor archivarlo en Github . Me gustaría decir "me gusta y suscribirme al nuevo canal , pero ¿todas sus versiones estarán en su centro Java de todos modos?"

Técnicamente: el video tiene un pegado más cerca del final. Acabo de escribir un video sin comprimir, y mi m2 ssd de solo quinientos gigabytes de tamaño se desbordó rápidamente. Y ningún otro disco duro podría soportar tanta presión de datos. Por lo tanto, tuve que desconectarme durante media hora y salí y encontré cincuenta conciertos adicionales para grabar los últimos minutos. Esto se logró eliminando los archivos compilados por GoogleChrome . Escribí sobre el software de grabación en el FB justo en el momento de la grabación , hay mucho dolor.

Otro de los técnicamente interesantes: YouTube por alguna razón me bloqueó la transmisión en vivo. Al mismo tiempo, no hay un solo golpe y estigma en la cuenta. Esperemos que esto sea solo una jamba, y después de 90 días todo será devuelto.

Este artículo hará referencia a un código propiedad de Oracle. No puede usar este código para usted (a menos que lea la licencia original y lo permita bajo las condiciones de, por ejemplo, la GPL). Esto no es una broma. Olso, advertí.

Prikazka (y un cuento de hadas estará por delante)


Muchos ya han escuchado historias de que "el nuevo Java se escribirá en Java" y se preguntan cómo podría ser esto. Hay un documento de política del Proyecto Metrópolis y una carta correspondiente de John Rose , pero todo es bastante vago allí.

Suena como una especie de magia espeluznante y sangrienta. En la misma cosa que puedes probar en este momento, no solo no hay magia, sino que todo es estúpido como la parte posterior de una pala cuando te sacas los dientes con ella. Por supuesto, hay algunos matices, pero esto será algún día muy tarde.

Lo mostraré en el ejemplo de una historia instructiva que sucedió en el verano. Cómo escriben el ensayo "Cómo pasé el verano" en las escuelas.

Para comenzar un pequeño comentario. El proyecto que actualmente realiza la compilación anticipada en Oracle Labs es GraalVM. El componente que, de hecho, hace nishtyaki y convierte el código Java en un archivo ejecutable (en un ejecutable) es SubstrateVM o SVM para abreviar. No confunda esto con la misma abreviatura utilizada por los satanistas de datos (máquina de vectores de soporte). Eso es sobre SVM, como parte clave, hablaremos más.

Declaración del problema.


Entonces, "cómo pasé el verano". Me senté de vacaciones, pasé dos F5 en el github del Grial y me encontré con este :



Una persona quiere que os.version dé el valor correcto.

Bueno cho, ¿quería arreglar el error? El niño dijo, el niño hizo.

Vamos a verificar si nuestro cliente está mintiendo.

 public class Main { public static void main(String[] args) { System.out.println(System.getProperty("os.version")); } } 

Primero, cómo se ve el escape en Java real: 4.15.0-32-generic . Sí, esto es Ubuntu LTS Bionic nuevo.

Ahora intenta hacer lo mismo en SVM:

 $ ls Main.java $ javac -cp . Main.java $ ls Main.class Main.java $ native-image Main Build on Server(pid: 18438, port: 35415) classlist: 151.77 ms (cap): 1,662.32 ms setup: 1,880.78 ms error: Basic header file missing (<zlib.h>). Make sure libc and zlib headers are available on your system. Error: Processing image build request failed 

Pues si. Esto se debe a que hice una máquina virtual completamente nueva específicamente para la prueba "limpia".

 $ sudo apt-get install zlib1g-dev libc6 libc6-dev $ native-image Main Build on Server(pid: 18438, port: 35415) classlist: 135.17 ms (cap): 877.34 ms setup: 1,253.49 ms (typeflow): 4,103.97 ms (objects): 1,441.97 ms (features): 41.74 ms analysis: 5,690.63 ms universe: 252.43 ms (parse): 1,024.49 ms (inline): 819.27 ms (compile): 4,243.15 ms compile: 6,356.02 ms image: 632.29 ms write: 236.99 ms [total]: 14,591.30 ms 

Las cifras absolutas de tiempo de ejecución pueden ser horribles. Pero, en primer lugar, eso es lo que se pretendía hacer: aquí se aplican optimizaciones muy infernales. Y en segundo lugar, esta es una máquina virtual frágil que desea.

Y finalmente, el momento de la verdad:

 $ ./main null 

Parece que nuestro invitado no mintió, realmente no funciona.

Primer enfoque: robar propiedades del host


Luego busqué en os.version global os.version y descubrí que todas estas propiedades están en la clase SystemPropertiesSupport .

No escribiré la ruta completa al archivo, porque la capacidad de generar los proyectos correctos para IntelliJ IDEA y Eclipse está integrada directamente en SVM. Esto es muy bueno y no recuerda en absoluto el tormento que la mayoría de OpenJDK tiene que experimentar. Deje que el IDE abra clases para nosotros. Entonces

 public abstract class SystemPropertiesSupport { private static final String[] HOSTED_PROPERTIES = { "java.version", ImageInfo.PROPERTY_IMAGE_KIND_KEY, "line.separator", "path.separator", "file.separator", "os.arch", "os.name", "file.encoding", "sun.jnu.encoding", }; //... } 

Luego, sin incluir mi cabeza en absoluto, simplemente fui y agregué otra variable a este conjunto:

 "os.arch", "os.name", "os.version" 

Reconstruyo, corro, obtengo la codiciada línea 4.15.0-32-generic . ¡Hurra!

Pero aquí está el problema: ahora en cada máquina donde se ejecuta este código, siempre emite 4.15.0-32-generic . Incluso donde uname -a devuelve la versión anterior del cubo, en el viejo Ubunt.

Queda claro que estas variables se escriben en el archivo fuente en el momento de la compilación.
Y realmente, necesitas leer cuidadosamente los comentarios:

 /** System properties that are taken from the VM hosting the image generator. */ private static final String[] HOSTED_PROPERTIES 

Otros métodos deben ser aplicados.

Conclusiones


  • Si desea que una propiedad del sistema de "Java principal" aparezca en SVM, esto es muy simple. Escribimos la propiedad deseada en el lugar correcto, eso es todo.
  • Puede trabajar en el IDE, que admite Java y Python al mismo tiempo. Por ejemplo, en IntelliJ IDEA Ultimate con un complemento de Python o el mismo en Eclipse.

Segundo enfoque


Si revisa el SystemPropertiesSupport SystemPropertiesSupport, encontramos algo mucho más razonable:

 /** System properties that are lazily computed at run time on first access. */ private final Map<String, Supplier<String>> lazyRuntimeValues; 

Entre otras cosas, el uso de estas propiedades aún no bloquea el proceso de construcción de un ejecutable. Está claro que si nos HOSTED_PROPERTIES mucho en HOSTED_PROPERTIES , entonces todo se ralentizará.

El registro de propiedades diferidas se produce de manera obvia, por referencia a un método que devuelve:

 lazyRuntimeValues.put("user.name", this::userNameValue); lazyRuntimeValues.put("user.home", this::userHomeValue); lazyRuntimeValues.put("user.dir", this::userDirValue); 

Además, todas estas referencias de métodos son interfaces, y lo mismo this::userDirValue se implementa para cada una de las plataformas compatibles. En este caso, estos son PosixSystemPropertiesSupport y WindowsSystemPropertiesSupport .

Si vamos por curiosidad a una implementación para Windows, veremos lo triste:

 @Override protected String userDirValue() { return "C:\\Users\\somebody"; } 

Como puede ver, Windows aún no es compatible :-) Sin embargo, el verdadero problema es que la generación de ejecutables para Windows aún no se ha completado, por lo que admitir estos métodos sería un esfuerzo completamente innecesario.

Es decir, debe implementar el siguiente método:

 lazyRuntimeValues.put("os.version", this::osVersionValue); 

Y luego admitirlo en dos o tres interfaces disponibles.

¿Pero qué escribir allí?

Conclusiones


  • Si desea agregar una nueva propiedad que se calcula en tiempo de ejecución, entonces se trata de escribir un método. El resultado puede depender del sistema operativo actual, el mecanismo de conmutación ya está funcionando y no pregunta.

Un poco de arqueología


Lo primero que viene a la mente es echar un vistazo a una implementación en OpenJDK y copiar y pegar descaradamente. ¡Un poco de arqueología y saqueo nunca impedirá a un valiente explorador!

Siéntase libre de abrir cualquier proyecto Java en la Idea, escriba System.getProperty("os.version") y, mediante ctrl + clic, vaya a la implementación del método getProperty() . Resulta que todo esto estúpidamente reside en las Properties .

Al parecer, solo copie y pegue el lugar donde se llenan estas Properties , y riéndose fervientemente, huya al vacío. Desafortunadamente, nos encontramos con un problema:

 private static native Properties initProperties(Properties props); 

Noooooooooooooo.



Pero todo comenzó muy bien.

¿Había un niño?


Como sabemos, usar C ++ es malo. ¿Se usa C ++ en SVM?

Solo asi! Incluso hay un paquete especial para esto: src/com.oracle.svm.native .

Y en este paquete, horror-horror, se encuentra el archivo getEnviron.c con algo como esto:

 extern char **environ; char **getEnviron() { return environ; } 

Es hora de meterse con C ++


Ahora sumérgete un poco más y abre las fuentes completas de OpenJDK.

Si alguien aún no los tiene, puede buscarlos en la web o descargarlos. Te lo advierto, están oscilando desde aquí , todavía con la ayuda de Mercurial, y aún así llevará aproximadamente media hora.

El archivo que necesitamos está en src/java.base/share/native/libjava/System.c .

¿Notó que esta es la ruta al archivo, y no solo el nombre? Así es, puede empujar su nueva y brillante Idea de moda, comprada por $ 200 al año. Puede probar CLion , pero para evitar daños mentales irreversibles, es mejor simplemente tomar Visual Studio Code . Él ya resalta algo, pero aún no entiende lo que vio (no tacha todo en rojo).

Breve recuento de System.c :

 java_props_t *sprops = GetJavaProperties(env); PUTPROP(props, "os.version", sprops->os_version); 

A su vez, se toman en src/java.base/unix/native/libjava/java_props_md.c .
Cada plataforma tiene su propio archivo, cambian a través de #define .

Y aquí comienza. Hay muchas plataformas Cualquier necrofilia como AIX se puede puntuar, porque GraalVM no lo admite oficialmente (hasta donde yo sé, GNU-Linux, macOS y Windows están planeados al principio). GNU / Linux y Windows admiten el uso de <sys/utsname.h> , que tiene métodos predefinidos para obtener el nombre y la versión del sistema operativo.

Pero macOS tiene una terrible pieza de govnokod .

  • El nombre "Mac OS X" está recubierto (aunque ha sido macOS durante mucho tiempo);
  • Depende de la versión de makoshi. Antes de 10.9, el SDK no tenía una función operatingSystemVersion , y tenía que leer SystemVersion.plist mano;
  • Para esta sustracción, usa la extensión ObjC algo como esto:

 // Fallback if running on pre-10.9 Mac OS if (osVersionCStr == NULL) { NSDictionary *version = [NSDictionary dictionaryWithContentsOfFile : @"/System/Library/CoreServices/SystemVersion.plist"]; if (version != NULL) { NSString *nsVerStr = [version objectForKey : @"ProductVersion"]; if (nsVerStr != NULL) { osVersionCStr = strdup([nsVerStr UTF8String]); } } } 

Si inicialmente hubo una idea de reescribir esto manualmente en un buen estilo, entonces rápidamente se convirtió en realidad. ¿Qué pasa si estoy jugando en algún lugar en la jungla de estos fideos de ifs, alguien lo romperá y me colgarán en la plaza central? Bueno nafig. Es necesario copiar y pegar.

Conclusiones


  • IDE no es necesario;
  • Cualquier comunicación con C ++ es dolorosa, desagradable, no se entiende a primera vista.

Copiar y pegar es la norma?


Este es un tema importante del que dependía la cantidad de tormento adicional. Realmente no quería volver a escribir manualmente, pero llegar a los tribunales por violar las licencias es aún peor. Así que fui al github y le pregunté a Codrut Stancu al respecto directamente. Aquí está lo que respondió :

»Reutilizar el código OpenJDK, por ejemplo, copiar y pegar es algo normal desde el punto de vista de las licencias. Sin embargo, hay una muy buena razón para esto. Si se puede implementar una característica reutilizando el código JDK sin copiar, por ejemplo, parcheándolo con sustitución, será mucho mejor ".

¡Eso suena como un permiso oficial de copiar y pegar!

Hablamos normalmente ...


Comencé a portar este código, pero me encontré con mi pereza. Para probar macOS para diferentes versiones, debe encontrar al menos uno con el 10.8 Mountain Lion necrófilo. Tengo dos de mis dispositivos Apple disponibles y uno de un amigo, además puedo implementarlo en alguna VMWare de prueba.

Pero la pereza. Y esta pereza me salvó.

Fui a la sala de chat y le pregunté a Chris Seaton qué cadena de herramientas es la más adecuada para el ensamblaje. Qué versión del sistema operativo es compatible, compilador de C ++, etc.

En respuesta, recibió un silencio sorprendido del chat y Chris respondió que no entendía la esencia del problema.

Tomó un poco de tiempo antes de que Chris pudiera darse cuenta de lo que quería hacer, y le pedí que nunca lo volviera a hacer .
Eso realmente extraña la idea de SVM. SVM es Java puro, no se supone que tenga código puesto desde OpenJDK. Puede leerlo, convertirlo a Java, pero nadie quiere el código C ++ de OpenJDK. Eso es lo último que queremos.

El ejemplo de las bibliotecas de matemáticas no lo convenció. Como mínimo, están escritos en C, y la inclusión de C ++ significaría conectar un lenguaje completamente nuevo a la base del código. Y uno que es fufufu.

Que necesitas hacer Escribir en el sistema Java .

Y si no se pueden evitar las llamadas al SDK de la plataforma C / C ++, entonces esta debería ser una sola llamada al sistema envuelta en la API de C. Los datos se extraen en Java y luego la lógica empresarial se escribe estrictamente en Java, incluso si Platform SDK tiene formas convenientes y listas para hacerlo de manera diferente en el lado de C ++.

Suspiré y comencé a estudiar el código fuente para comprender cómo se puede hacer esto de manera diferente.

Conclusiones


  • Hable con las personas en el chat sobre cualquier detalle oscuro. Responden si las preguntas no son completamente idiotas. Aunque este ejemplo muestra que Chris está listo para discutir cuestiones idiotas, incluso si esto no le ahorra su tiempo personalmente;
  • C ++ no está presente en el proyecto en absoluto. No hay razón para creer que alguien lo dejará arrastrarlo debajo del piso;
  • En cambio, debe escribir en Java del sistema utilizando C como último recurso (por ejemplo, al llamar al SDK de la plataforma).

No se necesita violinista


No se necesita un violinista, querido. Él solo come exceso de combustible.

Luego me sentí abrumado por la tristeza, porque mira aquí. Si en Windows tenemos <sys/utsname.h> , y esperamos estúpidamente su respuesta, es fácil y simple.

Pero si él no está allí, ¿qué habrá que hacer?

  • ¿Llamar a los comandos integrados de cmd o a las utilidades de Windows? Emisión de texto en ruso que debe analizarse. Este es el fondo, y puede que no coincida con lo que el OpenJDK real responderá en este lugar.
  • ¿Tomar del registro? Incluso aquí hay matices, por ejemplo, al cambiar de Windows 7 a 10, el método de almacenamiento de números digitales en el Registro cambió, y en Windows 10 debe pegar las manos de los componentes principales y secundarios, o simplemente responder que es Windows 10 con un dígito. No está claro cuál de estos métodos es más correcto (no hará que los usuarios se arrepientan).

Afortunadamente, mi angustia fue interrumpida por la búsqueda de extracción de Paul Woegerer, que lo arregló todo.

Curiosamente, al principio todo se reparó en el asistente (la os.version dejó de ser null en la prueba), y solo entonces noté una solicitud de extracción. El problema es que este commit no está marcado en el github como pullrequest; es un commit simple con la inscripción PullRequest: graal/1885 en el comentario. El hecho es que los tipos de Oracle Labs no usan Github, solo lo necesitan para interactuar con los encargados externos. Todos los que no tuvimos la suerte de trabajar en Oracle Labs debemos suscribirnos a las notificaciones de nuevos compromisos en el repositorio y leerlos todos.

Pero ahora puede relajarse y ver cómo implementar esta función correctamente .

Veamos qué tipo de bestia es esta, System Java.

Como dije antes, todo es simple, como la parte posterior de una pala, cuando intentan sacarte los dientes. E igual de doloroso. Echa un vistazo a la cita del grupo:

 @Override protected String osVersionValue() { if (osVersionValue != null) { return osVersionValue; } /* On OSX Java returns the ProductVersion instead of kernel release info. */ CoreFoundation.CFDictionaryRef dict = CoreFoundation._CFCopyServerVersionDictionary(); if (dict.isNull()) { dict = CoreFoundation._CFCopySystemVersionDictionary(); } if (dict.isNull()) { return osVersionValue = "Unknown"; } CoreFoundation.CFStringRef dictKeyRef = DarwinCoreFoundationUtils.toCFStringRef("MacOSXProductVersion"); CoreFoundation.CFStringRef dictValue = CoreFoundation.CFDictionaryGetValue(dict, dictKeyRef); CoreFoundation.CFRelease(dictKeyRef); if (dictValue.isNull()) { dictKeyRef = DarwinCoreFoundationUtils.toCFStringRef("ProductVersion"); dictValue = CoreFoundation.CFDictionaryGetValue(dict, dictKeyRef); CoreFoundation.CFRelease(dictKeyRef); } if (dictValue.isNull()) { return osVersionValue = "Unknown"; } osVersionValue = DarwinCoreFoundationUtils.fromCFStringRef(dictValue); CoreFoundation.CFRelease(dictValue); return osVersionValue; } 

En otras palabras, escribimos en Java palabra por palabra lo que escribiríamos en C.

DarwinExecutableName a cómo DarwinExecutableName escribe DarwinExecutableName :

  @Override public Object apply(Object[] args) { /* Find out how long the executable path is. */ final CIntPointer sizePointer = StackValue.get(CIntPointer.class); sizePointer.write(0); if (DarwinDyld._NSGetExecutablePath(WordFactory.nullPointer(), sizePointer) != -1) { VMError.shouldNotReachHere("DarwinExecutableName.getExecutableName: Executable path length is 0?"); } /* Allocate a correctly-sized buffer and ask again. */ final byte[] byteBuffer = new byte[sizePointer.read()]; try (PinnedObject pinnedBuffer = PinnedObject.create(byteBuffer)) { final CCharPointer bufferPointer = pinnedBuffer.addressOfArrayElement(0); if (DarwinDyld._NSGetExecutablePath(bufferPointer, sizePointer) == -1) { /* Failure to find executable path. */ return null; } final String executableString = CTypeConversion.toJavaString(bufferPointer); final String result = realpath(executableString); return result; } } 

Todos estos CIntPointer , CCharPointer , PinnedObject , qué.

Para mi gusto, esto es incómodo y feo. Debe trabajar manualmente con punteros que parecen clases de Java. Debe llamar a la release correspondiente a tiempo para que no se pierda la memoria.

Pero si le parece que estas son medidas injustificadas , puede volver a mirar la implementación de GC en .NET y horrorizarse de lo que lleva C ++ si no se detiene a tiempo. Les recuerdo que este es un gran archivo CPP más grande que un megabyte. Hay algunas descripciones de su trabajo, pero son claramente insuficientes para que un colaborador externo las entienda. El código anterior, aunque de aspecto desagradable, es bastante comprensible y se analiza mediante análisis estático para Java.

En cuanto a la esencia del compromiso, tengo preguntas para él. Y al menos el soporte de Windows no está implementado allí. Cuando aparezca un codegen para Windows, intentaré asumir esta tarea.

Conclusiones


  • Necesito escribir en System Java. Alabado sea el pan dulce. Todavía no hay opciones;
  • Suscríbase a las notificaciones del repositorio en GitHub y lea las confirmaciones; de lo contrario, pasarán por alto importantes relaciones públicas;
  • Si es posible, pregunte sobre cualquier característica importante de los responsables de esta área. Hay muchas cosas que se implementan, pero aún no son conocidas por el público en general. Existe la posibilidad de inventar una bicicleta, y mucho peor que la hecha por los muchachos de Oracle Labs;
  • Al abordar una función, asegúrese de informar a la persona responsable en el github. Si no responde, escriba una carta, las direcciones de todos los miembros del equipo se pueden buscar fácilmente en Google.

Epílogo


Esta batalla termina, pero no la guerra en absoluto.

¡Luchador, espera con delicadeza nuevos artículos sobre Habré y entra en nuestras filas !

Quiero recordarles que Oleg Shelaev, el único evangelista oficial de GraalVM de Oracle, vendrá a la próxima conferencia de Joker . No solo "el único hablante ruso", sino "el único en general". El título del informe ( "Compilación de Java por adelantado con GraalVM" ) sugiere que SubstrateVM no puede prescindir.

Por cierto, Oleg recibió recientemente un arma de servicio: una cuenta en Habré, shelajev-oleg . Todavía no hay publicaciones allí, pero puedes enviar este nombre de usuario.

Puedes hablar con Oleg y Oleg en nuestra sala de chat-chat en Telegram: @graalvm_ru . A diferencia de ishshuyev en Github, allí puede comunicarse de cualquier forma, y ​​nadie será prohibido ( pero esto no es exacto ).

También les recuerdo que cada semana, junto con el podcast Debriefing, hacemos el lanzamiento de Java Digest. Por ejemplo, este fue el último resumen . De vez en cuando, las noticias sobre GraalVM pasan por alto (de hecho, no convierto todo el tema en un comunicado de prensa de GraalVM solo por respeto a la audiencia :-)

Gracias por leer esto, ¡y hasta pronto!

Source: https://habr.com/ru/post/es420455/


All Articles