[json-glib: 2/3] json-parser: Support loading files via memory mapping



commit dd7a711244e3d33e3e960cd990e9afccc6877820
Author: Philip Withnall <withnall endlessm com>
Date:   Tue Jun 9 15:41:45 2020 +0100

    json-parser: Support loading files via memory mapping
    
    Add a new `json_parser_load_from_mapped_file()` to load JSON from
    files via memory mapping. It’s otherwise similar to
    `json_parser_load_from_file()`. It’s in the right position to be able
    to memory map the file it’s reading from: it reads the input once
    before building a `JsonNode` structure to represent it, doesn’t write
    to the file, and often deals with large input files.
    
    This should speed things up slightly due to reducing time spent
    allocating a large chunk of heap memory to load the file into, if a
    caller can support that.
    
    Signed-off-by: Philip Withnall <withnall endlessm com>

 doc/json-glib-sections.txt   |  1 +
 json-glib/json-parser.c      | 58 ++++++++++++++++++++++++++++++++++++++++++++
 json-glib/json-parser.h      |  4 +++
 json-glib/tests/invalid.json |  1 +
 json-glib/tests/meson.build  |  1 +
 json-glib/tests/parser.c     | 56 ++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 121 insertions(+)
---
diff --git a/doc/json-glib-sections.txt b/doc/json-glib-sections.txt
index ada0895..ced06d0 100644
--- a/doc/json-glib-sections.txt
+++ b/doc/json-glib-sections.txt
@@ -183,6 +183,7 @@ JsonParserClass
 json_parser_new
 json_parser_new_immutable
 json_parser_load_from_file
+json_parser_load_from_mapped_file
 json_parser_load_from_data
 json_parser_load_from_stream
 json_parser_load_from_stream_async
diff --git a/json-glib/json-parser.c b/json-glib/json-parser.c
index a90bbd8..3d77975 100644
--- a/json-glib/json-parser.c
+++ b/json-glib/json-parser.c
@@ -1089,6 +1089,9 @@ json_parser_load (JsonParser   *parser,
  * Loads a JSON stream from the content of @filename and parses it. See
  * json_parser_load_from_data().
  *
+ * If the file is large or shared between processes,
+ * json_parser_load_from_mapped_file() may be a more efficient way to load it.
+ *
  * Return value: %TRUE if the file was successfully loaded and parsed.
  *   In case of error, @error is set accordingly and %FALSE is returned
  */
@@ -1131,6 +1134,61 @@ json_parser_load_from_file (JsonParser   *parser,
   return retval;
 }
 
+/**
+ * json_parser_load_from_mapped_file:
+ * @parser: a #JsonParser
+ * @filename: the path for the file to parse
+ * @error: return location for a #GError, or %NULL
+ *
+ * Loads a JSON stream from the content of @filename and parses it. Unlike
+ * json_parser_load_from_file(), @filename will be memory mapped as read-only
+ * and parsed. @filename will be unmapped before this function returns.
+ *
+ * If mapping or reading the file fails, a %G_FILE_ERROR will be returned.
+ *
+ * Return value: %TRUE if the file was successfully loaded and parsed.
+ *   In case of error, @error is set accordingly and %FALSE is returned
+ * Since: 1.6
+ */
+gboolean
+json_parser_load_from_mapped_file (JsonParser   *parser,
+                                   const gchar  *filename,
+                                   GError      **error)
+{
+  JsonParserPrivate *priv;
+  GError *internal_error = NULL;
+  gboolean retval = TRUE;
+  GMappedFile *mapped_file = NULL;
+
+  g_return_val_if_fail (JSON_IS_PARSER (parser), FALSE);
+  g_return_val_if_fail (filename != NULL, FALSE);
+
+  priv = parser->priv;
+
+  mapped_file = g_mapped_file_new (filename, FALSE, &internal_error);
+  if (mapped_file == NULL)
+    {
+      g_propagate_error (error, internal_error);
+      return FALSE;
+    }
+
+  g_free (priv->filename);
+
+  priv->is_filename = TRUE;
+  priv->filename = g_strdup (filename);
+
+  if (!json_parser_load (parser, g_mapped_file_get_contents (mapped_file),
+                         g_mapped_file_get_length (mapped_file), &internal_error))
+    {
+      g_propagate_error (error, internal_error);
+      retval = FALSE;
+    }
+
+  g_clear_pointer (&mapped_file, g_mapped_file_unref);
+
+  return retval;
+}
+
 /**
  * json_parser_load_from_data:
  * @parser: a #JsonParser
diff --git a/json-glib/json-parser.h b/json-glib/json-parser.h
index 1470fbf..91fde66 100644
--- a/json-glib/json-parser.h
+++ b/json-glib/json-parser.h
@@ -153,6 +153,10 @@ JSON_AVAILABLE_IN_1_0
 gboolean    json_parser_load_from_file          (JsonParser           *parser,
                                                  const gchar          *filename,
                                                  GError              **error);
+JSON_AVAILABLE_IN_1_6
+gboolean    json_parser_load_from_mapped_file   (JsonParser           *parser,
+                                                 const gchar          *filename,
+                                                 GError              **error);
 JSON_AVAILABLE_IN_1_0
 gboolean    json_parser_load_from_data          (JsonParser           *parser,
                                                  const gchar          *data,
diff --git a/json-glib/tests/invalid.json b/json-glib/tests/invalid.json
new file mode 100644
index 0000000..1634764
--- /dev/null
+++ b/json-glib/tests/invalid.json
@@ -0,0 +1 @@
+nope
diff --git a/json-glib/tests/meson.build b/json-glib/tests/meson.build
index 881d902..7fdbc3f 100644
--- a/json-glib/tests/meson.build
+++ b/json-glib/tests/meson.build
@@ -16,6 +16,7 @@ tests = [
 ]
 
 test_data = [
+  'invalid.json',
   'stream-load.json',
 ]
 
diff --git a/json-glib/tests/parser.c b/json-glib/tests/parser.c
index acc6276..ddec577 100644
--- a/json-glib/tests/parser.c
+++ b/json-glib/tests/parser.c
@@ -754,6 +754,59 @@ test_stream_async (void)
   g_free (path);
 }
 
+/* Test json_parser_load_from_mapped_file() succeeds. */
+static void
+test_mapped (void)
+{
+  GError *error = NULL;
+  JsonParser *parser = json_parser_new ();
+  char *path;
+
+  path = g_test_build_filename (G_TEST_DIST, "stream-load.json", NULL);
+
+  json_parser_load_from_mapped_file (parser, path, &error);
+  g_assert_no_error (error);
+
+  assert_stream_load_json_correct (parser);
+
+  g_object_unref (parser);
+  g_free (path);
+}
+
+/* Test json_parser_load_from_mapped_file() error handling for file I/O. */
+static void
+test_mapped_file_error (void)
+{
+  GError *error = NULL;
+  JsonParser *parser = json_parser_new ();
+
+  json_parser_load_from_mapped_file (parser, "nope.json", &error);
+  g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_NOENT);
+
+  g_assert_null (json_parser_get_root (parser));
+
+  g_object_unref (parser);
+}
+
+/* Test json_parser_load_from_mapped_file() error handling for JSON parsing. */
+static void
+test_mapped_json_error (void)
+{
+  GError *error = NULL;
+  JsonParser *parser = json_parser_new ();
+  char *path;
+
+  path = g_test_build_filename (G_TEST_DIST, "invalid.json", NULL);
+
+  json_parser_load_from_mapped_file (parser, path, &error);
+  g_assert_error (error, JSON_PARSER_ERROR, JSON_PARSER_ERROR_INVALID_BAREWORD);
+
+  g_assert_null (json_parser_get_root (parser));
+
+  g_object_unref (parser);
+  g_free (path);
+}
+
 int
 main (int   argc,
       char *argv[])
@@ -772,6 +825,9 @@ main (int   argc,
   g_test_add_func ("/parser/unicode-escape", test_unicode_escape);
   g_test_add_func ("/parser/stream-sync", test_stream_sync);
   g_test_add_func ("/parser/stream-async", test_stream_async);
+  g_test_add_func ("/parser/mapped", test_mapped);
+  g_test_add_func ("/parser/mapped/file-error", test_mapped_file_error);
+  g_test_add_func ("/parser/mapped/json-error", test_mapped_json_error);
 
   return g_test_run ();
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]