1 //  Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
2 //  This source code is licensed under both the GPLv2 (found in the
3 //  COPYING file in the root directory) and Apache 2.0 License
4 //  (found in the LICENSE.Apache file in the root directory).
5 //
6 // This file implements the callback "bridge" between Java and C++ for
7 // rocksdb::EventListener.
8 
9 #include "rocksjni/event_listener_jnicallback.h"
10 
11 #include "rocksjni/portal.h"
12 
13 namespace rocksdb {
EventListenerJniCallback(JNIEnv * env,jobject jevent_listener,const std::set<EnabledEventCallback> & enabled_event_callbacks)14 EventListenerJniCallback::EventListenerJniCallback(
15     JNIEnv* env, jobject jevent_listener,
16     const std::set<EnabledEventCallback>& enabled_event_callbacks)
17     : JniCallback(env, jevent_listener),
18       m_enabled_event_callbacks(enabled_event_callbacks) {
19   InitCallbackMethodId(
20       m_on_flush_completed_proxy_mid, EnabledEventCallback::ON_FLUSH_COMPLETED,
21       env, AbstractEventListenerJni::getOnFlushCompletedProxyMethodId);
22 
23   InitCallbackMethodId(m_on_flush_begin_proxy_mid,
24                        EnabledEventCallback::ON_FLUSH_BEGIN, env,
25                        AbstractEventListenerJni::getOnFlushBeginProxyMethodId);
26 
27   InitCallbackMethodId(m_on_table_file_deleted_mid,
28                        EnabledEventCallback::ON_TABLE_FILE_DELETED, env,
29                        AbstractEventListenerJni::getOnTableFileDeletedMethodId);
30 
31   InitCallbackMethodId(
32       m_on_compaction_begin_proxy_mid,
33       EnabledEventCallback::ON_COMPACTION_BEGIN, env,
34       AbstractEventListenerJni::getOnCompactionBeginProxyMethodId);
35 
36   InitCallbackMethodId(
37       m_on_compaction_completed_proxy_mid,
38       EnabledEventCallback::ON_COMPACTION_COMPLETED, env,
39       AbstractEventListenerJni::getOnCompactionCompletedProxyMethodId);
40 
41   InitCallbackMethodId(m_on_table_file_created_mid,
42                        EnabledEventCallback::ON_TABLE_FILE_CREATED, env,
43                        AbstractEventListenerJni::getOnTableFileCreatedMethodId);
44 
45   InitCallbackMethodId(
46       m_on_table_file_creation_started_mid,
47       EnabledEventCallback::ON_TABLE_FILE_CREATION_STARTED, env,
48       AbstractEventListenerJni::getOnTableFileCreationStartedMethodId);
49 
50   InitCallbackMethodId(m_on_mem_table_sealed_mid,
51                        EnabledEventCallback::ON_MEMTABLE_SEALED, env,
52                        AbstractEventListenerJni::getOnMemTableSealedMethodId);
53 
54   InitCallbackMethodId(
55       m_on_column_family_handle_deletion_started_mid,
56       EnabledEventCallback::ON_COLUMN_FAMILY_HANDLE_DELETION_STARTED, env,
57       AbstractEventListenerJni::getOnColumnFamilyHandleDeletionStartedMethodId);
58 
59   InitCallbackMethodId(
60       m_on_external_file_ingested_proxy_mid,
61       EnabledEventCallback::ON_EXTERNAL_FILE_INGESTED, env,
62       AbstractEventListenerJni::getOnExternalFileIngestedProxyMethodId);
63 
64   InitCallbackMethodId(
65       m_on_background_error_proxy_mid,
66       EnabledEventCallback::ON_BACKGROUND_ERROR, env,
67       AbstractEventListenerJni::getOnBackgroundErrorProxyMethodId);
68 
69   InitCallbackMethodId(
70       m_on_stall_conditions_changed_mid,
71       EnabledEventCallback::ON_STALL_CONDITIONS_CHANGED, env,
72       AbstractEventListenerJni::getOnStallConditionsChangedMethodId);
73 
74   InitCallbackMethodId(m_on_file_read_finish_mid,
75                        EnabledEventCallback::ON_FILE_READ_FINISH, env,
76                        AbstractEventListenerJni::getOnFileReadFinishMethodId);
77 
78   InitCallbackMethodId(m_on_file_write_finish_mid,
79                        EnabledEventCallback::ON_FILE_WRITE_FINISH, env,
80                        AbstractEventListenerJni::getOnFileWriteFinishMethodId);
81 
82   InitCallbackMethodId(m_on_file_flush_finish_mid,
83                        EnabledEventCallback::ON_FILE_FLUSH_FINISH, env,
84                        AbstractEventListenerJni::getOnFileFlushFinishMethodId);
85 
86   InitCallbackMethodId(m_on_file_sync_finish_mid,
87                        EnabledEventCallback::ON_FILE_SYNC_FINISH, env,
88                        AbstractEventListenerJni::getOnFileSyncFinishMethodId);
89 
90   InitCallbackMethodId(
91       m_on_file_range_sync_finish_mid,
92       EnabledEventCallback::ON_FILE_RANGE_SYNC_FINISH, env,
93       AbstractEventListenerJni::getOnFileRangeSyncFinishMethodId);
94 
95   InitCallbackMethodId(
96       m_on_file_truncate_finish_mid,
97       EnabledEventCallback::ON_FILE_TRUNCATE_FINISH, env,
98       AbstractEventListenerJni::getOnFileTruncateFinishMethodId);
99 
100   InitCallbackMethodId(m_on_file_close_finish_mid,
101                        EnabledEventCallback::ON_FILE_CLOSE_FINISH, env,
102                        AbstractEventListenerJni::getOnFileCloseFinishMethodId);
103 
104   InitCallbackMethodId(
105       m_should_be_notified_on_file_io,
106       EnabledEventCallback::SHOULD_BE_NOTIFIED_ON_FILE_IO, env,
107       AbstractEventListenerJni::getShouldBeNotifiedOnFileIOMethodId);
108 
109   InitCallbackMethodId(
110       m_on_error_recovery_begin_proxy_mid,
111       EnabledEventCallback::ON_ERROR_RECOVERY_BEGIN, env,
112       AbstractEventListenerJni::getOnErrorRecoveryBeginProxyMethodId);
113 
114   InitCallbackMethodId(
115       m_on_error_recovery_completed_mid,
116       EnabledEventCallback::ON_ERROR_RECOVERY_COMPLETED, env,
117       AbstractEventListenerJni::getOnErrorRecoveryCompletedMethodId);
118 }
119 
~EventListenerJniCallback()120 EventListenerJniCallback::~EventListenerJniCallback() {}
121 
OnFlushCompleted(DB * db,const FlushJobInfo & flush_job_info)122 void EventListenerJniCallback::OnFlushCompleted(
123     DB* db, const FlushJobInfo& flush_job_info) {
124   if (m_on_flush_completed_proxy_mid == nullptr) {
125     return;
126   }
127 
128   JNIEnv* env;
129   jboolean attached_thread;
130   jobject jflush_job_info = SetupCallbackInvocation<FlushJobInfo>(
131       env, attached_thread, flush_job_info,
132       FlushJobInfoJni::fromCppFlushJobInfo);
133 
134   if (jflush_job_info != nullptr) {
135     env->CallVoidMethod(m_jcallback_obj, m_on_flush_completed_proxy_mid,
136                         reinterpret_cast<jlong>(db), jflush_job_info);
137   }
138 
139   CleanupCallbackInvocation(env, attached_thread, {&jflush_job_info});
140 }
141 
OnFlushBegin(DB * db,const FlushJobInfo & flush_job_info)142 void EventListenerJniCallback::OnFlushBegin(
143     DB* db, const FlushJobInfo& flush_job_info) {
144   if (m_on_flush_begin_proxy_mid == nullptr) {
145     return;
146   }
147 
148   JNIEnv* env;
149   jboolean attached_thread;
150   jobject jflush_job_info = SetupCallbackInvocation<FlushJobInfo>(
151       env, attached_thread, flush_job_info,
152       FlushJobInfoJni::fromCppFlushJobInfo);
153 
154   if (jflush_job_info != nullptr) {
155     env->CallVoidMethod(m_jcallback_obj, m_on_flush_begin_proxy_mid,
156                         reinterpret_cast<jlong>(db), jflush_job_info);
157   }
158 
159   CleanupCallbackInvocation(env, attached_thread, {&jflush_job_info});
160 }
161 
OnTableFileDeleted(const TableFileDeletionInfo & info)162 void EventListenerJniCallback::OnTableFileDeleted(
163     const TableFileDeletionInfo& info) {
164   if (m_on_table_file_deleted_mid == nullptr) {
165     return;
166   }
167 
168   JNIEnv* env;
169   jboolean attached_thread;
170   jobject jdeletion_info = SetupCallbackInvocation<TableFileDeletionInfo>(
171       env, attached_thread, info,
172       TableFileDeletionInfoJni::fromCppTableFileDeletionInfo);
173 
174   if (jdeletion_info != nullptr) {
175     env->CallVoidMethod(m_jcallback_obj, m_on_table_file_deleted_mid,
176                         jdeletion_info);
177   }
178 
179   CleanupCallbackInvocation(env, attached_thread, {&jdeletion_info});
180 }
181 
OnCompactionBegin(DB * db,const CompactionJobInfo & ci)182 void EventListenerJniCallback::OnCompactionBegin(DB* db,
183                                                  const CompactionJobInfo& ci) {
184   if (m_on_compaction_begin_proxy_mid == nullptr) {
185     return;
186   }
187 
188   JNIEnv* env;
189   jboolean attached_thread;
190   jobject jcompaction_job_info = SetupCallbackInvocation<CompactionJobInfo>(
191       env, attached_thread, ci, CompactionJobInfoJni::fromCppCompactionJobInfo);
192 
193   if (jcompaction_job_info != nullptr) {
194     env->CallVoidMethod(m_jcallback_obj, m_on_compaction_begin_proxy_mid,
195                         reinterpret_cast<jlong>(db), jcompaction_job_info);
196   }
197 
198   CleanupCallbackInvocation(env, attached_thread, {&jcompaction_job_info});
199 }
200 
OnCompactionCompleted(DB * db,const CompactionJobInfo & ci)201 void EventListenerJniCallback::OnCompactionCompleted(
202     DB* db, const CompactionJobInfo& ci) {
203   if (m_on_compaction_completed_proxy_mid == nullptr) {
204     return;
205   }
206 
207   JNIEnv* env;
208   jboolean attached_thread;
209   jobject jcompaction_job_info = SetupCallbackInvocation<CompactionJobInfo>(
210       env, attached_thread, ci, CompactionJobInfoJni::fromCppCompactionJobInfo);
211 
212   if (jcompaction_job_info != nullptr) {
213     env->CallVoidMethod(m_jcallback_obj, m_on_compaction_completed_proxy_mid,
214                         reinterpret_cast<jlong>(db), jcompaction_job_info);
215   }
216 
217   CleanupCallbackInvocation(env, attached_thread, {&jcompaction_job_info});
218 }
219 
OnTableFileCreated(const TableFileCreationInfo & info)220 void EventListenerJniCallback::OnTableFileCreated(
221     const TableFileCreationInfo& info) {
222   if (m_on_table_file_created_mid == nullptr) {
223     return;
224   }
225 
226   JNIEnv* env;
227   jboolean attached_thread;
228   jobject jfile_creation_info = SetupCallbackInvocation<TableFileCreationInfo>(
229       env, attached_thread, info,
230       TableFileCreationInfoJni::fromCppTableFileCreationInfo);
231 
232   if (jfile_creation_info != nullptr) {
233     env->CallVoidMethod(m_jcallback_obj, m_on_table_file_created_mid,
234                         jfile_creation_info);
235   }
236 
237   CleanupCallbackInvocation(env, attached_thread, {&jfile_creation_info});
238 }
239 
OnTableFileCreationStarted(const TableFileCreationBriefInfo & info)240 void EventListenerJniCallback::OnTableFileCreationStarted(
241     const TableFileCreationBriefInfo& info) {
242   if (m_on_table_file_creation_started_mid == nullptr) {
243     return;
244   }
245 
246   JNIEnv* env;
247   jboolean attached_thread;
248   jobject jcreation_brief_info =
249       SetupCallbackInvocation<TableFileCreationBriefInfo>(
250           env, attached_thread, info,
251           TableFileCreationBriefInfoJni::fromCppTableFileCreationBriefInfo);
252 
253   if (jcreation_brief_info != nullptr) {
254     env->CallVoidMethod(m_jcallback_obj, m_on_table_file_creation_started_mid,
255                         jcreation_brief_info);
256   }
257 
258   CleanupCallbackInvocation(env, attached_thread, {&jcreation_brief_info});
259 }
260 
OnMemTableSealed(const MemTableInfo & info)261 void EventListenerJniCallback::OnMemTableSealed(const MemTableInfo& info) {
262   if (m_on_mem_table_sealed_mid == nullptr) {
263     return;
264   }
265 
266   JNIEnv* env;
267   jboolean attached_thread;
268   jobject jmem_table_info = SetupCallbackInvocation<MemTableInfo>(
269       env, attached_thread, info, MemTableInfoJni::fromCppMemTableInfo);
270 
271   if (jmem_table_info != nullptr) {
272     env->CallVoidMethod(m_jcallback_obj, m_on_mem_table_sealed_mid,
273                         jmem_table_info);
274   }
275 
276   CleanupCallbackInvocation(env, attached_thread, {&jmem_table_info});
277 }
278 
OnColumnFamilyHandleDeletionStarted(ColumnFamilyHandle * handle)279 void EventListenerJniCallback::OnColumnFamilyHandleDeletionStarted(
280     ColumnFamilyHandle* handle) {
281   if (m_on_column_family_handle_deletion_started_mid == nullptr) {
282     return;
283   }
284 
285   JNIEnv* env;
286   jboolean attached_thread;
287   jobject jcf_handle = SetupCallbackInvocation<ColumnFamilyHandle>(
288       env, attached_thread, *handle,
289       ColumnFamilyHandleJni::fromCppColumnFamilyHandle);
290 
291   if (jcf_handle != nullptr) {
292     env->CallVoidMethod(m_jcallback_obj,
293                         m_on_column_family_handle_deletion_started_mid,
294                         jcf_handle);
295   }
296 
297   CleanupCallbackInvocation(env, attached_thread, {&jcf_handle});
298 }
299 
OnExternalFileIngested(DB * db,const ExternalFileIngestionInfo & info)300 void EventListenerJniCallback::OnExternalFileIngested(
301     DB* db, const ExternalFileIngestionInfo& info) {
302   if (m_on_external_file_ingested_proxy_mid == nullptr) {
303     return;
304   }
305 
306   JNIEnv* env;
307   jboolean attached_thread;
308   jobject jingestion_info = SetupCallbackInvocation<ExternalFileIngestionInfo>(
309       env, attached_thread, info,
310       ExternalFileIngestionInfoJni::fromCppExternalFileIngestionInfo);
311 
312   if (jingestion_info != nullptr) {
313     env->CallVoidMethod(m_jcallback_obj, m_on_external_file_ingested_proxy_mid,
314                         reinterpret_cast<jlong>(db), jingestion_info);
315   }
316 
317   CleanupCallbackInvocation(env, attached_thread, {&jingestion_info});
318 }
319 
OnBackgroundError(BackgroundErrorReason reason,Status * bg_error)320 void EventListenerJniCallback::OnBackgroundError(BackgroundErrorReason reason,
321                                                  Status* bg_error) {
322   if (m_on_background_error_proxy_mid == nullptr) {
323     return;
324   }
325 
326   JNIEnv* env;
327   jboolean attached_thread;
328   jobject jstatus = SetupCallbackInvocation<Status>(
329       env, attached_thread, *bg_error, StatusJni::construct);
330 
331   if (jstatus != nullptr) {
332     env->CallVoidMethod(m_jcallback_obj, m_on_background_error_proxy_mid,
333                         static_cast<jbyte>(reason), jstatus);
334   }
335 
336   CleanupCallbackInvocation(env, attached_thread, {&jstatus});
337 }
338 
OnStallConditionsChanged(const WriteStallInfo & info)339 void EventListenerJniCallback::OnStallConditionsChanged(
340     const WriteStallInfo& info) {
341   if (m_on_stall_conditions_changed_mid == nullptr) {
342     return;
343   }
344 
345   JNIEnv* env;
346   jboolean attached_thread;
347   jobject jwrite_stall_info = SetupCallbackInvocation<WriteStallInfo>(
348       env, attached_thread, info, WriteStallInfoJni::fromCppWriteStallInfo);
349 
350   if (jwrite_stall_info != nullptr) {
351     env->CallVoidMethod(m_jcallback_obj, m_on_stall_conditions_changed_mid,
352                         jwrite_stall_info);
353   }
354 
355   CleanupCallbackInvocation(env, attached_thread, {&jwrite_stall_info});
356 }
357 
OnFileReadFinish(const FileOperationInfo & info)358 void EventListenerJniCallback::OnFileReadFinish(const FileOperationInfo& info) {
359   OnFileOperation(m_on_file_read_finish_mid, info);
360 }
361 
OnFileWriteFinish(const FileOperationInfo & info)362 void EventListenerJniCallback::OnFileWriteFinish(
363     const FileOperationInfo& info) {
364   OnFileOperation(m_on_file_write_finish_mid, info);
365 }
366 
OnFileFlushFinish(const FileOperationInfo & info)367 void EventListenerJniCallback::OnFileFlushFinish(
368     const FileOperationInfo& info) {
369   OnFileOperation(m_on_file_flush_finish_mid, info);
370 }
371 
OnFileSyncFinish(const FileOperationInfo & info)372 void EventListenerJniCallback::OnFileSyncFinish(const FileOperationInfo& info) {
373   OnFileOperation(m_on_file_sync_finish_mid, info);
374 }
375 
OnFileRangeSyncFinish(const FileOperationInfo & info)376 void EventListenerJniCallback::OnFileRangeSyncFinish(
377     const FileOperationInfo& info) {
378   OnFileOperation(m_on_file_range_sync_finish_mid, info);
379 }
380 
OnFileTruncateFinish(const FileOperationInfo & info)381 void EventListenerJniCallback::OnFileTruncateFinish(
382     const FileOperationInfo& info) {
383   OnFileOperation(m_on_file_truncate_finish_mid, info);
384 }
385 
OnFileCloseFinish(const FileOperationInfo & info)386 void EventListenerJniCallback::OnFileCloseFinish(
387     const FileOperationInfo& info) {
388   OnFileOperation(m_on_file_close_finish_mid, info);
389 }
390 
ShouldBeNotifiedOnFileIO()391 bool EventListenerJniCallback::ShouldBeNotifiedOnFileIO() {
392   if (m_should_be_notified_on_file_io == nullptr) {
393     return false;
394   }
395 
396   jboolean attached_thread = JNI_FALSE;
397   JNIEnv* env = getJniEnv(&attached_thread);
398   assert(env != nullptr);
399 
400   jboolean jshould_be_notified =
401       env->CallBooleanMethod(m_jcallback_obj, m_should_be_notified_on_file_io);
402 
403   CleanupCallbackInvocation(env, attached_thread, {});
404 
405   return static_cast<bool>(jshould_be_notified);
406 }
407 
OnErrorRecoveryBegin(BackgroundErrorReason reason,Status bg_error,bool * auto_recovery)408 void EventListenerJniCallback::OnErrorRecoveryBegin(
409     BackgroundErrorReason reason, Status bg_error, bool* auto_recovery) {
410   if (m_on_error_recovery_begin_proxy_mid == nullptr) {
411     return;
412   }
413 
414   JNIEnv* env;
415   jboolean attached_thread;
416   jobject jbg_error = SetupCallbackInvocation<Status>(
417       env, attached_thread, bg_error, StatusJni::construct);
418 
419   if (jbg_error != nullptr) {
420     jboolean jauto_recovery = env->CallBooleanMethod(
421         m_jcallback_obj, m_on_error_recovery_begin_proxy_mid,
422         static_cast<jbyte>(reason), jbg_error);
423     *auto_recovery = jauto_recovery == JNI_TRUE;
424   }
425 
426   CleanupCallbackInvocation(env, attached_thread, {&jbg_error});
427 }
428 
OnErrorRecoveryCompleted(Status old_bg_error)429 void EventListenerJniCallback::OnErrorRecoveryCompleted(Status old_bg_error) {
430   if (m_on_error_recovery_completed_mid == nullptr) {
431     return;
432   }
433 
434   JNIEnv* env;
435   jboolean attached_thread;
436   jobject jold_bg_error = SetupCallbackInvocation<Status>(
437       env, attached_thread, old_bg_error, StatusJni::construct);
438 
439   if (jold_bg_error != nullptr) {
440     env->CallVoidMethod(m_jcallback_obj, m_on_error_recovery_completed_mid,
441                         jold_bg_error);
442   }
443 
444   CleanupCallbackInvocation(env, attached_thread, {&jold_bg_error});
445 }
446 
InitCallbackMethodId(jmethodID & mid,EnabledEventCallback eec,JNIEnv * env,jmethodID (* get_id)(JNIEnv * env))447 void EventListenerJniCallback::InitCallbackMethodId(
448     jmethodID& mid, EnabledEventCallback eec, JNIEnv* env,
449     jmethodID (*get_id)(JNIEnv* env)) {
450   if (m_enabled_event_callbacks.count(eec) == 1) {
451     mid = get_id(env);
452   } else {
453     mid = nullptr;
454   }
455 }
456 
457 template <class T>
SetupCallbackInvocation(JNIEnv * & env,jboolean & attached_thread,const T & cpp_obj,jobject (* convert)(JNIEnv * env,const T * cpp_obj))458 jobject EventListenerJniCallback::SetupCallbackInvocation(
459     JNIEnv*& env, jboolean& attached_thread, const T& cpp_obj,
460     jobject (*convert)(JNIEnv* env, const T* cpp_obj)) {
461   attached_thread = JNI_FALSE;
462   env = getJniEnv(&attached_thread);
463   assert(env != nullptr);
464 
465   return convert(env, &cpp_obj);
466 }
467 
CleanupCallbackInvocation(JNIEnv * env,jboolean attached_thread,std::initializer_list<jobject * > refs)468 void EventListenerJniCallback::CleanupCallbackInvocation(
469     JNIEnv* env, jboolean attached_thread,
470     std::initializer_list<jobject*> refs) {
471   for (auto* ref : refs) {
472     if (*ref == nullptr) continue;
473     env->DeleteLocalRef(*ref);
474   }
475 
476   if (env->ExceptionCheck()) {
477     // exception thrown from CallVoidMethod
478     env->ExceptionDescribe();  // print out exception to stderr
479   }
480 
481   releaseJniEnv(attached_thread);
482 }
483 
OnFileOperation(const jmethodID & mid,const FileOperationInfo & info)484 void EventListenerJniCallback::OnFileOperation(const jmethodID& mid,
485                                                const FileOperationInfo& info) {
486   if (mid == nullptr) {
487     return;
488   }
489 
490   JNIEnv* env;
491   jboolean attached_thread;
492   jobject jop_info = SetupCallbackInvocation<FileOperationInfo>(
493       env, attached_thread, info,
494       FileOperationInfoJni::fromCppFileOperationInfo);
495 
496   if (jop_info != nullptr) {
497     env->CallVoidMethod(m_jcallback_obj, mid, jop_info);
498   }
499 
500   CleanupCallbackInvocation(env, attached_thread, {&jop_info});
501 }
502 }  // namespace rocksdb
503