You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by bb...@apache.org on 2019/04/30 18:11:12 UTC

[geode-native] branch develop updated: GEODE-6624: fix nested exception causing SIGABRT (#478)

This is an automated email from the ASF dual-hosted git repository.

bbender pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode-native.git


The following commit(s) were added to refs/heads/develop by this push:
     new cc75539  GEODE-6624: fix nested exception causing SIGABRT (#478)
cc75539 is described below

commit cc75539abea0a77d1f9fa4f70db0d26b20be6bb8
Author: Blake Bender <bb...@pivotal.io>
AuthorDate: Tue Apr 30 11:11:08 2019 -0700

    GEODE-6624: fix nested exception causing SIGABRT (#478)
    
    * GEODE-6624: Fix nested exception in geode-native causing SIGABRT
    - Add test case to reproduce original bug.
    - Generated a function to return string constants
    - Added a note to corresponding server bug
    - Server team will need to change the type of exception expected in the test to repro their bug
    
    Co-authored-by: Matthew Reddington <mr...@pivotal.io>
---
 CMakeLists.txt                                     |   2 +-
 .../integration-test2/FunctionExecutionTest.cs     |  25 +++++
 cppcache/CMakeLists.txt                            |   2 +-
 cppcache/integration/framework/CMakeLists.txt      |  11 +-
 cppcache/integration/framework/Gfsh.h              |  14 +++
 cppcache/integration/framework/GfshExecute.cpp     |   2 +-
 cppcache/integration/framework/GfshExecute.h       |   2 +-
 cppcache/integration/framework/TestConfig.cpp      |   0
 .../framework/{config.h.in => TestConfig.cpp.in}   |  15 +--
 .../framework/{config.h.in => TestConfig.h}        |   6 +-
 cppcache/integration/framework/config.h.in         |   1 +
 .../integration/test/FunctionExecutionTest.cpp     |  65 ++++++++---
 cppcache/src/TcrChunkedContext.hpp                 |   2 +
 cppcache/src/TcrConnection.cpp                     | 122 +++++++++++----------
 tests/javaobject/NonDeserializableObject.java      |  67 +++++++++++
 ...Function_SendObjectWhichCantBeDeserialized.java |  66 +++++++++++
 16 files changed, 313 insertions(+), 89 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index b0ce016..f3dd50c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -348,6 +348,7 @@ set_target_properties(run-integration-tests PROPERTIES
 
 find_package(OpenSSL REQUIRED)
 
+add_subdirectory(tests/javaobject)
 add_subdirectory(dependencies)
 add_subdirectory(openssl-compat)
 add_subdirectory(cppcache)
@@ -357,7 +358,6 @@ add_subdirectory(sqliteimpl)
 add_subdirectory(templates/security)
 add_subdirectory(docs/api)
 add_subdirectory(examples)
-add_subdirectory(tests/javaobject)
 if (${BUILD_CLI})
   add_subdirectory(clicache)
 endif()
diff --git a/clicache/integration-test2/FunctionExecutionTest.cs b/clicache/integration-test2/FunctionExecutionTest.cs
index 6688d31..81b4018 100644
--- a/clicache/integration-test2/FunctionExecutionTest.cs
+++ b/clicache/integration-test2/FunctionExecutionTest.cs
@@ -132,5 +132,30 @@ namespace Apache.Geode.Client.IntegrationTests
                 Assert.Equal(expectedResultCount, resultList.Count);
             }
         }
+
+    [Fact(Skip = "Waiting for a fix from Geode server.")]
+    public void FunctionReturnsObjectWhichCantBeDeserializedOnServer()
+    {
+      using (var cluster = new Cluster(output, CreateTestCaseDirectoryName(), 1, 2))
+      {
+        Assert.True(cluster.Start());
+        Assert.Equal(0, cluster.Gfsh.create().region()
+            .withName("region")
+            .withType("REPLICATE")
+            .execute());
+        Assert.Equal(0, cluster.Gfsh.deploy()
+            .withJar(Config.JavaobjectJarPath)
+            .execute());
+
+        var cache = cluster.CreateCache();
+        var pool = cache.GetPoolFactory().AddLocator("localhost", 10334).Create("pool");
+        var region = cache.CreateRegionFactory(RegionShortcut.PROXY)
+            .SetPoolName("pool")
+            .Create<object, object>("region");
+
+        var exc = Client.FunctionService<List<object>>.OnRegion<object, object>(region);
+        Assert.Throws<FunctionExecutionException>(() => exc.Execute("executeFunction_SendObjectWhichCantBeDeserialized"));
+      }
     }
+  }
 }
diff --git a/cppcache/CMakeLists.txt b/cppcache/CMakeLists.txt
index fc357bb..da62541 100644
--- a/cppcache/CMakeLists.txt
+++ b/cppcache/CMakeLists.txt
@@ -132,4 +132,4 @@ add_subdirectory(integration-test)
 
 if (BUILD_BENCHMARKS)
   add_subdirectory(benchmark)
-endif()
\ No newline at end of file
+endif()
diff --git a/cppcache/integration/framework/CMakeLists.txt b/cppcache/integration/framework/CMakeLists.txt
index 8aa234c..7b4385b 100644
--- a/cppcache/integration/framework/CMakeLists.txt
+++ b/cppcache/integration/framework/CMakeLists.txt
@@ -13,19 +13,20 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-configure_file(config.h.in config.h)
+get_target_property(JAVAOBJECT_JAR_PATH javaobject JAR_FILE)
+
+configure_file(TestConfig.cpp.in TestConfig.cpp)
 
 add_library(integration-framework STATIC
   Gfsh.cpp
-  Gfsh.h
   Framework.cpp
-  Framework.h
   Cluster.cpp
-  Cluster.h
   GfshExecute.cpp
-  GfshExecute.h
+  ${CMAKE_CURRENT_BINARY_DIR}/TestConfig.cpp
   )
 
+add_dependencies(integration-framework javaobject)
+
 target_compile_definitions(integration-framework
   PUBLIC
     BOOST_ASIO_HAS_MOVE
diff --git a/cppcache/integration/framework/Gfsh.h b/cppcache/integration/framework/Gfsh.h
index 20d9713..c03831f 100644
--- a/cppcache/integration/framework/Gfsh.h
+++ b/cppcache/integration/framework/Gfsh.h
@@ -45,6 +45,9 @@ class Gfsh {
   class Shutdown;
   Shutdown shutdown() { return Shutdown{*this}; }
 
+  class Deploy;
+  Deploy deploy() { return Deploy(*this); }
+
   class Verb {
    public:
    protected:
@@ -252,6 +255,17 @@ class Gfsh {
     };
   };
 
+  class Deploy : public Command<void> {
+   public:
+    explicit Deploy(Gfsh &gfsh) : Command{gfsh, "deploy"} {}
+
+    Deploy &jar(const std::string &jarFile) {
+      command_ += " --jars=" + jarFile;
+
+      return *this;
+    }
+  };
+
  protected:
   virtual void execute(const std::string &command) = 0;
 };
diff --git a/cppcache/integration/framework/GfshExecute.cpp b/cppcache/integration/framework/GfshExecute.cpp
index 5ae9023..4ea1b55 100644
--- a/cppcache/integration/framework/GfshExecute.cpp
+++ b/cppcache/integration/framework/GfshExecute.cpp
@@ -80,6 +80,6 @@ child GfshExecute::executeChild(std::vector<std::string> &commands,
   // https://github.com/klemens-morgenstern/boost-process/issues/159
   std::lock_guard<std::mutex> guard(g_child_mutex);
 #endif
-  return child(GFSH_EXECUTABLE, args = commands, env, std_out > outStream,
+  return child(getFrameworkString(FrameworkVariable::GfShExecutable), args = commands, env, std_out > outStream,
                std_err > errStream);
 }
diff --git a/cppcache/integration/framework/GfshExecute.h b/cppcache/integration/framework/GfshExecute.h
index 39f95e9..11922fe 100644
--- a/cppcache/integration/framework/GfshExecute.h
+++ b/cppcache/integration/framework/GfshExecute.h
@@ -34,7 +34,7 @@
                        w_constexprnonlitret, explctspectypename)
 
 #include "Gfsh.h"
-#include "config.h"
+#include "TestConfig.h"
 
 template <class _T>
 bool starts_with(const _T &input, const _T &match) {
diff --git a/cppcache/integration/framework/TestConfig.cpp b/cppcache/integration/framework/TestConfig.cpp
new file mode 100644
index 0000000..e69de29
diff --git a/cppcache/integration/framework/config.h.in b/cppcache/integration/framework/TestConfig.cpp.in
similarity index 73%
copy from cppcache/integration/framework/config.h.in
copy to cppcache/integration/framework/TestConfig.cpp.in
index 935175f..567a290 100644
--- a/cppcache/integration/framework/config.h.in
+++ b/cppcache/integration/framework/TestConfig.cpp.in
@@ -15,11 +15,12 @@
  * limitations under the License.
  */
 
-#pragma once
+#include "framework/TestConfig.h"
 
-#ifndef INTEGRATION_TEST_FRAMEWORK_CONFIG_H
-#define INTEGRATION_TEST_FRAMEWORK_CONFIG_H
-
-#define GFSH_EXECUTABLE "@Geode_gfsh_EXECUTABLE@"
-
-#endif  // INTEGRATION_TEST_FRAMEWORK_CONFIG_H
+const char *getFrameworkString(FrameworkVariable name) {
+  switch(name) {
+    case FrameworkVariable::JavaObjectJarPath: return "@JAVAOBJECT_JAR_PATH@";
+    case FrameworkVariable::GfShExecutable: return "@Geode_gfsh_EXECUTABLE@";
+    default: return "";
+  }
+}
diff --git a/cppcache/integration/framework/config.h.in b/cppcache/integration/framework/TestConfig.h
similarity index 84%
copy from cppcache/integration/framework/config.h.in
copy to cppcache/integration/framework/TestConfig.h
index 935175f..aa4d81e 100644
--- a/cppcache/integration/framework/config.h.in
+++ b/cppcache/integration/framework/TestConfig.h
@@ -20,6 +20,8 @@
 #ifndef INTEGRATION_TEST_FRAMEWORK_CONFIG_H
 #define INTEGRATION_TEST_FRAMEWORK_CONFIG_H
 
-#define GFSH_EXECUTABLE "@Geode_gfsh_EXECUTABLE@"
+enum class FrameworkVariable {JavaObjectJarPath, GfShExecutable};
 
-#endif  // INTEGRATION_TEST_FRAMEWORK_CONFIG_H
+const char *getFrameworkString(FrameworkVariable name);
+
+#endif // INTEGRATION_TEST_FRAMEWORK_CONFIG_H
diff --git a/cppcache/integration/framework/config.h.in b/cppcache/integration/framework/config.h.in
index 935175f..0ddbd5b 100644
--- a/cppcache/integration/framework/config.h.in
+++ b/cppcache/integration/framework/config.h.in
@@ -21,5 +21,6 @@
 #define INTEGRATION_TEST_FRAMEWORK_CONFIG_H
 
 #define GFSH_EXECUTABLE "@Geode_gfsh_EXECUTABLE@"
+#define JAVAOBJECT_JAR_PATH "@JAVAOBJECT_JAR_PATH@"
 
 #endif  // INTEGRATION_TEST_FRAMEWORK_CONFIG_H
diff --git a/cppcache/integration/test/FunctionExecutionTest.cpp b/cppcache/integration/test/FunctionExecutionTest.cpp
index 06b9e02..8640882 100644
--- a/cppcache/integration/test/FunctionExecutionTest.cpp
+++ b/cppcache/integration/test/FunctionExecutionTest.cpp
@@ -14,10 +14,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 #include <gtest/gtest.h>
 
 #include <geode/Cache.hpp>
+#include <geode/CacheFactory.hpp>
+#include <geode/CacheableBuiltins.hpp>
 #include <geode/ExceptionTypes.hpp>
 #include <geode/FunctionService.hpp>
 #include <geode/PoolManager.hpp>
@@ -26,12 +27,15 @@
 
 #include "framework/Cluster.h"
 #include "framework/Gfsh.h"
+#include "framework/TestConfig.h"
 
 using apache::geode::client::Cache;
 using apache::geode::client::Cacheable;
 using apache::geode::client::CacheableVector;
+using apache::geode::client::CacheFactory;
 using apache::geode::client::FunctionExecutionException;
 using apache::geode::client::FunctionService;
+using apache::geode::client::NotConnectedException;
 using apache::geode::client::Region;
 using apache::geode::client::RegionShortcut;
 using apache::geode::client::ResultCollector;
@@ -44,6 +48,19 @@ std::shared_ptr<Region> setupRegion(Cache &cache) {
   return region;
 }
 
+class TestResultCollector : public ResultCollector {
+  virtual std::shared_ptr<CacheableVector> getResult(
+      std::chrono::milliseconds) override {
+    return std::shared_ptr<CacheableVector>();
+  }
+
+  virtual void addResult(const std::shared_ptr<Cacheable> &) override {}
+
+  virtual void endResults() override {}
+
+  virtual void clearResults() override {}
+};
+
 TEST(FunctionExecutionTest, UnknownFunctionOnServer) {
   Cluster cluster{LocatorCount{1}, ServerCount{1}};
   cluster.getGfsh()
@@ -77,19 +94,6 @@ TEST(FunctionExecutionTest, UnknownFunctionOnRegion) {
                FunctionExecutionException);
 }
 
-class TestResultCollector : public ResultCollector {
-  virtual std::shared_ptr<CacheableVector> getResult(
-      std::chrono::milliseconds) override {
-    return std::shared_ptr<CacheableVector>();
-  }
-
-  virtual void addResult(const std::shared_ptr<Cacheable> &) override {}
-
-  virtual void endResults() override {}
-
-  virtual void clearResults() override {}
-};
-
 TEST(FunctionExecutionTest, UnknownFunctionAsyncOnServer) {
   Cluster cluster{LocatorCount{1}, ServerCount{1}};
   cluster.getGfsh()
@@ -125,3 +129,36 @@ TEST(FunctionExecutionTest, UnknownFunctionAsyncOnRegion) {
                    .execute("I_Don_t_Exist"),
                FunctionExecutionException);
 }
+
+TEST(FunctionExecutionTest,
+     FunctionReturnsObjectWhichCantBeDeserializedOnServer) {
+  Cluster cluster{LocatorCount{1}, ServerCount{2}};
+  cluster.getGfsh()
+      .create()
+      .region()
+      .withName("region")
+      .withType("REPLICATE")
+      .execute();
+
+  cluster.getGfsh()
+      .deploy()
+      .jar(getFrameworkString(FrameworkVariable::JavaObjectJarPath))
+      .execute();
+
+  auto cache = CacheFactory().set("log-level", "none").create();
+  auto pool = cache.getPoolManager()
+                  .createFactory()
+                  .addLocator("localhost", 10334)
+                  .create("pool");
+  auto region = cache.createRegionFactory(RegionShortcut::PROXY)
+                    .setPoolName("pool")
+                    .create("region");
+
+  const char *GetScopeSnapshotsFunction =
+      "executeFunction_SendObjectWhichCantBeDeserialized";
+  auto functionService = FunctionService::onRegion(region);
+  ASSERT_THROW(functionService.execute(GetScopeSnapshotsFunction),
+               NotConnectedException);
+
+  cache.close();
+}
diff --git a/cppcache/src/TcrChunkedContext.hpp b/cppcache/src/TcrChunkedContext.hpp
index e5df84c..ab8614f 100644
--- a/cppcache/src/TcrChunkedContext.hpp
+++ b/cppcache/src/TcrChunkedContext.hpp
@@ -111,6 +111,8 @@ class TcrChunkedResult {
   inline void setException(std::shared_ptr<Exception> ex) { m_ex = ex; }
 
   inline std::shared_ptr<Exception>& getException() { return m_ex; }
+
+  inline void clearException() { m_ex = nullptr; }
 };
 
 /**
diff --git a/cppcache/src/TcrConnection.cpp b/cppcache/src/TcrConnection.cpp
index b38bc25..2089580 100644
--- a/cppcache/src/TcrConnection.cpp
+++ b/cppcache/src/TcrConnection.cpp
@@ -963,77 +963,85 @@ void TcrConnection::readMessageChunked(
    public:
     FinalizeProcessChunk(TcrMessageReply& reply, uint16_t endpointmemId)
         : m_reply(reply), m_endpointmemId(endpointmemId) {}
-    ~FinalizeProcessChunk() {
+    ~FinalizeProcessChunk() noexcept(false) {
       // Enqueue a nullptr chunk indicating a wait for processing to complete.
       m_reply.processChunk(nullptr, 0, m_endpointmemId);
     }
   } endProcessChunk(reply, m_endpointObj->getDistributedMemberID());
-  bool first = true;
-  do {
-    // uint8_t chunk_header[HDR_LEN];
-    if (!first) {
-      error = receiveData(reinterpret_cast<char*>(msg_header + HDR_LEN_12),
-                          HDR_LEN, headerTimeout, true, false);
+
+  try {
+    bool first = true;
+    do {
+      // uint8_t chunk_header[HDR_LEN];
+      if (!first) {
+        error = receiveData(reinterpret_cast<char*>(msg_header + HDR_LEN_12),
+                            HDR_LEN, headerTimeout, true, false);
+        if (error != CONN_NOERR) {
+          if (error & CONN_TIMEOUT) {
+            throwException(TimeoutException(
+                "TcrConnection::readMessageChunked: "
+                "connection timed out while receiving chunk header"));
+          } else {
+            throwException(GeodeIOException(
+                "TcrConnection::readMessageChunked: "
+                "connection failure while receiving chunk header"));
+          }
+        }
+      } else {
+        first = false;
+      }
+      ++chunkNum;
+
+      LOGDEBUG(
+          "TcrConnection::readMessageChunked: received chunk header %d "
+          "from endpoint %s; bytes: %s",
+          chunkNum, m_endpoint,
+          Utils::convertBytesToString((msg_header + HDR_LEN_12), HDR_LEN)
+
+              .c_str());
+
+      auto input = m_connectionManager->getCacheImpl()->createDataInput(
+          msg_header + HDR_LEN_12, HDR_LEN);
+      int32_t chunkLen;
+      chunkLen = input.readInt32();
+      //  check that chunk length is valid.
+      GF_DEV_ASSERT(chunkLen > 0);
+      isLastChunk = input.read();
+
+      uint8_t* chunk_body;
+      _GEODE_NEW(chunk_body, uint8_t[chunkLen]);
+      error = receiveData(reinterpret_cast<char*>(chunk_body), chunkLen,
+                          receiveTimeoutSec, true, false);
       if (error != CONN_NOERR) {
+        delete[] chunk_body;
         if (error & CONN_TIMEOUT) {
           throwException(TimeoutException(
               "TcrConnection::readMessageChunked: "
-              "connection timed out while receiving chunk header"));
+              "connection timed out while receiving chunk body"));
         } else {
           throwException(GeodeIOException(
               "TcrConnection::readMessageChunked: "
-              "connection failure while receiving chunk header"));
+              "connection failure while receiving chunk body"));
         }
       }
-    } else {
-      first = false;
-    }
-    ++chunkNum;
-
-    LOGDEBUG(
-        "TcrConnection::readMessageChunked: received chunk header %d "
-        "from endpoint %s; bytes: %s",
-        chunkNum, m_endpoint,
-        Utils::convertBytesToString((msg_header + HDR_LEN_12), HDR_LEN)
-
-            .c_str());
-
-    auto input = m_connectionManager->getCacheImpl()->createDataInput(
-        msg_header + HDR_LEN_12, HDR_LEN);
-    int32_t chunkLen;
-    chunkLen = input.readInt32();
-    //  check that chunk length is valid.
-    GF_DEV_ASSERT(chunkLen > 0);
-    isLastChunk = input.read();
-
-    uint8_t* chunk_body;
-    _GEODE_NEW(chunk_body, uint8_t[chunkLen]);
-    error = receiveData(reinterpret_cast<char*>(chunk_body), chunkLen,
-                        receiveTimeoutSec, true, false);
-    if (error != CONN_NOERR) {
-      delete[] chunk_body;
-      if (error & CONN_TIMEOUT) {
-        throwException(TimeoutException(
-            "TcrConnection::readMessageChunked: "
-            "connection timed out while receiving chunk body"));
-      } else {
-        throwException(
-            GeodeIOException("TcrConnection::readMessageChunked: "
-                             "connection failure while receiving chunk body"));
-      }
-    }
 
-    LOGDEBUG(
-        "TcrConnection::readMessageChunked: received chunk body %d "
-        "from endpoint %s; bytes: %s",
-        chunkNum, m_endpoint,
-        Utils::convertBytesToString(chunk_body, chunkLen).c_str());
-    // Process the chunk; the actual processing is done by a separate thread
-    // ThinClientBaseDM::m_chunkProcessor.
-
-    reply.processChunk(chunk_body, chunkLen,
-                       m_endpointObj->getDistributedMemberID(), isLastChunk);
-  } while (!(isLastChunk & 0x1));
+      LOGDEBUG(
+          "TcrConnection::readMessageChunked: received chunk body %d "
+          "from endpoint %s; bytes: %s",
+          chunkNum, m_endpoint,
+          Utils::convertBytesToString(chunk_body, chunkLen).c_str());
+      // Process the chunk; the actual processing is done by a separate thread
+      // ThinClientBaseDM::m_chunkProcessor.
+
+      reply.processChunk(chunk_body, chunkLen,
+                         m_endpointObj->getDistributedMemberID(), isLastChunk);
+    } while (!(isLastChunk & 0x1));
+  } catch (const Exception&) {
+    auto ex = reply.getChunkedResultHandler()->getException();
+    LOGDEBUG("Found existing exception ", ex->what());
+    reply.getChunkedResultHandler()->clearException();
+    throw;
+  }
 
   LOGFINER(
       "TcrConnection::readMessageChunked: read full reply "
diff --git a/tests/javaobject/NonDeserializableObject.java b/tests/javaobject/NonDeserializableObject.java
new file mode 100644
index 0000000..786c71a
--- /dev/null
+++ b/tests/javaobject/NonDeserializableObject.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javaobject;
+
+import org.apache.geode.DataSerializable;
+import org.apache.geode.Instantiator;
+import org.apache.geode.cache.Declarable;
+import java.io.*;
+
+//
+// NonDeserializableObject is, in general, deserializable, but on a Geode server
+// it can't be deserialized because it has no default ctor, and thus can't be
+// instantiated via reflection.  This is interesting because it's possible to,
+// for instance, execute a function server-side which returns an instance of
+// this class, which causes Geode to return a payload of type 'DataSerializable'
+// with subtype 'Class', and the class name and data necessary to recreate the
+// object in a client whieh supports reflection.  Since C++ doesn't have this,
+// the Geode Native Client should throw an exception, and that's what we use
+// this class to test.
+//
+public class NonDeserializableObject implements DataSerializable  {
+    static {
+        Instantiator.register(new NonDeserializableObjectInstantiator());
+    }
+
+   String m_str;
+
+   public NonDeserializableObject(String str){m_str = str;}
+
+   @Override
+   public void toData(DataOutput dataOutput) throws IOException {
+   }
+
+   @Override
+   public void fromData(DataInput dataInput) throws IOException, ClassNotFoundException {
+   }
+
+   public static class NonDeserializableObjectInstantiator extends Instantiator {
+        public NonDeserializableObjectInstantiator() {
+            super(NonDeserializableObject.class, 500);
+        }
+
+        public NonDeserializableObjectInstantiator(Class<? extends DataSerializable> c, int classId) {
+            super(c, classId);
+        }
+
+        @Override
+        public DataSerializable newInstance() {
+            return new NonDeserializableObject("foo");
+        }
+    }
+}
+
diff --git a/tests/javaobject/executeFunction_SendObjectWhichCantBeDeserialized.java b/tests/javaobject/executeFunction_SendObjectWhichCantBeDeserialized.java
new file mode 100644
index 0000000..69aa716
--- /dev/null
+++ b/tests/javaobject/executeFunction_SendObjectWhichCantBeDeserialized.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javaobject;
+
+import org.apache.geode.DataSerializable;
+import org.apache.geode.Instantiator;
+import org.apache.geode.cache.Declarable;
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.execute.Function;
+import org.apache.geode.cache.execute.FunctionContext;
+import org.apache.geode.cache.execute.RegionFunctionContext;
+
+import java.util.Properties;
+import java.io.*;
+
+import javaobject.NonDeserializableObject;
+
+public class executeFunction_SendObjectWhichCantBeDeserialized implements Function, Declarable {
+
+    public static final String NAME = "executeFunction_SendObjectWhichCantBeDeserialized";
+
+    @Override
+    public void execute(FunctionContext context) {
+        NonDeserializableObject myNonDeserializableObject = new NonDeserializableObject("123");
+        context.getResultSender().lastResult(myNonDeserializableObject);
+    }
+
+    @Override
+    public boolean hasResult() {
+        return true;
+    }
+
+    @Override
+    public boolean optimizeForWrite() {
+        return true;
+    }
+
+    @Override
+    public String getId() {
+        return NAME;
+    }
+
+   @Override
+    public boolean isHA() {
+        return true;
+    }
+
+    @Override
+    public void init(Properties props) {
+    }
+}
+