From 9543df37cdfd35980c888440265d161e350d166d Mon Sep 17 00:00:00 2001 From: wang di Date: Mon, 20 Jul 2015 10:45:58 -0700 Subject: [PATCH] LU-6840 target: update reply data after update replay Set correct value to tgt_session_info, so tgt_txn_stop_cb()-> tgt_last_rcvd_update() can be called during update replay, and the reply and and last_rcvd can be updated during update replay. Signed-off-by: wang di Change-Id: I32e5b2a7d30aecfb1a20b8d31a0054a3c28ccd50 Reviewed-on: http://review.whamcloud.com/15576 Tested-by: Jenkins Reviewed-by: James Simmons Reviewed-by: Lai Siyao Tested-by: Maloo Reviewed-by: Oleg Drokin --- lustre/include/lu_target.h | 13 +++ lustre/ldlm/ldlm_lib.c | 96 ------------------- lustre/obdclass/genops.c | 11 +++ lustre/target/tgt_lastrcvd.c | 200 ++++++++++++++++++++++++---------------- lustre/target/update_recovery.c | 87 ++++++++++++++++- 5 files changed, 226 insertions(+), 181 deletions(-) diff --git a/lustre/include/lu_target.h b/lustre/include/lu_target.h index 65dfc3c..73eb250 100644 --- a/lustre/include/lu_target.h +++ b/lustre/include/lu_target.h @@ -60,6 +60,9 @@ struct distribute_txn_replay_req { /* all of sub updates are linked here */ struct list_head dtrq_sub_list; spinlock_t dtrq_sub_list_lock; + + /* If the local update has been executed during replay */ + __u32 dtrq_local_update_executed:1; }; /* Each one represents a sub replay item under a distribute @@ -216,6 +219,11 @@ struct tgt_session_info { bool tsi_preprocessed; /* request JobID */ char *tsi_jobid; + + /* update replay */ + __u64 tsi_xid; + __u32 tsi_result; + __u32 tsi_client_gen; }; static inline struct tgt_session_info *tgt_ses_info(const struct lu_env *env) @@ -437,6 +445,11 @@ int tgt_truncate_last_rcvd(const struct lu_env *env, struct lu_target *tg, loff_t off); int tgt_reply_data_init(const struct lu_env *env, struct lu_target *tgt); bool tgt_lookup_reply(struct ptlrpc_request *req, struct tg_reply_data *trd); +int tgt_add_reply_data(const struct lu_env *env, struct lu_target *tgt, + struct tg_export_data *ted, struct tg_reply_data *trd, + struct thandle *th, bool update_lrd_file); +struct tg_reply_data *tgt_lookup_reply_by_xid(struct tg_export_data *ted, + __u64 xid); /* target/update_trans.c */ int distribute_txn_init(const struct lu_env *env, diff --git a/lustre/ldlm/ldlm_lib.c b/lustre/ldlm/ldlm_lib.c index f56b452..9b51752 100644 --- a/lustre/ldlm/ldlm_lib.c +++ b/lustre/ldlm/ldlm_lib.c @@ -1571,7 +1571,6 @@ void target_cleanup_recovery(struct obd_device *obd) { struct ptlrpc_request *req, *n; struct list_head clean_list; - ENTRY; INIT_LIST_HEAD(&clean_list); spin_lock(&obd->obd_dev_lock); @@ -2135,100 +2134,6 @@ static void drop_duplicate_replay_req(struct lu_env *env, obd->obd_replayed_requests++; } -/** - * Update last_rcvd of the update - * - * Because update recovery might update the last_rcvd by updates, i.e. - * it will not update the last_rcvd information in memory, so we need - * refresh these information in memory after update recovery. - * - * \param[in] obd obd_device under recoverying. - * \param[in] dtrq the update replay requests being replayed. - */ -static void target_update_lcd(struct lu_env *env, struct lu_target *lut, - struct distribute_txn_replay_req *dtrq) -{ - struct obd_device *obd = lut->lut_obd; - struct obd_export *export; - struct tg_export_data *ted; - struct distribute_txn_replay_req_sub *dtrqs; - struct seq_server_site *site; - struct update_records *ur; - const struct lu_fid *fid; - struct update_ops *ops; - struct update_params *params; - struct update_op *op; - __u32 mdt_index; - unsigned int i; - struct lsd_client_data *lcd = NULL; - - /* if Updates has been executed(committed) on the recovery target, - * i.e. the updates is not being executed on the target, so we do - * not need update it in memory */ - site = lu_site2seq(obd->obd_lu_dev->ld_site); - mdt_index = site->ss_node_id; - dtrqs = dtrq_sub_lookup(dtrq, mdt_index); - if (dtrqs != NULL) - return; - - if (dtrq->dtrq_lur == NULL) - return; - - /* Find the update last_rcvd record */ - fid = lu_object_fid(&lut->lut_last_rcvd->do_lu); - ur = &dtrq->dtrq_lur->lur_update_rec; - ops = &ur->ur_ops; - params = update_records_get_params(ur); - for (i = 0, op = &ops->uops_op[0]; i < ur->ur_update_count; - i++, op = update_op_next_op(op)) { - __u64 pos; - __u16 size; - void *buf; - - if (!lu_fid_eq(&op->uop_fid, fid)) - continue; - - if (op->uop_type != OUT_WRITE) - continue; - - buf = update_params_get_param_buf(params, op->uop_params_off[1], - ur->ur_param_count, NULL); - if (buf == NULL) - continue; - - pos = le64_to_cpu(*(__u64 *)buf); - if (pos == 0) - continue; - - buf = update_params_get_param_buf(params, op->uop_params_off[0], - ur->ur_param_count, &size); - if (buf == NULL) - continue; - - if (size != sizeof(*lcd)) - continue; - lcd = buf; - } - - if (lcd == NULL || lcd->lcd_uuid[0] == '\0') - return; - - /* locate the export then update the exp_target_data if needed */ - export = cfs_hash_lookup(obd->obd_uuid_hash, lcd->lcd_uuid); - if (export == NULL) - return; - - ted = &export->exp_target_data; - if (lcd->lcd_last_xid > ted->ted_lcd->lcd_last_xid) { - CDEBUG(D_HA, "%s update xid from "LPU64" to "LPU64"\n", - lut->lut_obd->obd_name, ted->ted_lcd->lcd_last_xid, - lcd->lcd_last_xid); - ted->ted_lcd->lcd_last_xid = lcd->lcd_last_xid; - ted->ted_lcd->lcd_last_result = lcd->lcd_last_result; - } - class_export_put(export); -} - static void replay_request_or_update(struct lu_env *env, struct lu_target *lut, struct target_recovery_data *trd, @@ -2315,7 +2220,6 @@ static void replay_request_or_update(struct lu_env *env, if (transno > obd->obd_next_recovery_transno) obd->obd_next_recovery_transno = transno; spin_unlock(&obd->obd_recovery_task_lock); - target_update_lcd(env, lut, dtrq); dtrq_destroy(dtrq); } else { spin_unlock(&obd->obd_recovery_task_lock); diff --git a/lustre/obdclass/genops.c b/lustre/obdclass/genops.c index 44b313a..194781b 100644 --- a/lustre/obdclass/genops.c +++ b/lustre/obdclass/genops.c @@ -44,6 +44,7 @@ #include #include #include +#include #include spinlock_t obd_types_lock; @@ -936,6 +937,16 @@ void class_unlink_export(struct obd_export *exp) &exp->exp_client_uuid, &exp->exp_uuid_hash); + if (!hlist_unhashed(&exp->exp_gen_hash)) { + struct tg_export_data *ted = &exp->exp_target_data; + struct cfs_hash *hash; + + hash = cfs_hash_getref(exp->exp_obd->obd_gen_hash); + cfs_hash_del(hash, &ted->ted_lcd->lcd_generation, + &exp->exp_gen_hash); + cfs_hash_putref(hash); + } + list_move(&exp->exp_obd_chain, &exp->exp_obd->obd_unlinked_exports); list_del_init(&exp->exp_obd_chain_timed); exp->exp_obd->obd_num_exports--; diff --git a/lustre/target/tgt_lastrcvd.c b/lustre/target/tgt_lastrcvd.c index 2a45895..d1afc9d 100644 --- a/lustre/target/tgt_lastrcvd.c +++ b/lustre/target/tgt_lastrcvd.c @@ -1060,6 +1060,58 @@ int tgt_client_del(const struct lu_env *env, struct obd_export *exp) } EXPORT_SYMBOL(tgt_client_del); +int tgt_add_reply_data(const struct lu_env *env, struct lu_target *tgt, + struct tg_export_data *ted, struct tg_reply_data *trd, + struct thandle *th, bool update_lrd_file) +{ + struct lsd_reply_data *lrd; + int i; + + lrd = &trd->trd_reply; + /* update export last transno */ + mutex_lock(&ted->ted_lcd_lock); + if (lrd->lrd_transno > ted->ted_lcd->lcd_last_transno) + ted->ted_lcd->lcd_last_transno = lrd->lrd_transno; + mutex_unlock(&ted->ted_lcd_lock); + + /* find a empty slot */ + i = tgt_find_free_reply_slot(tgt); + if (unlikely(i < 0)) { + CERROR("%s: couldn't find a slot for reply data: " + "rc = %d\n", tgt_name(tgt), i); + RETURN(i); + } + trd->trd_index = i; + + if (update_lrd_file) { + loff_t off; + int rc; + + /* write reply data to disk */ + off = sizeof(struct lsd_reply_header) + sizeof(*lrd) * i; + rc = tgt_reply_data_write(env, tgt, lrd, off, th); + if (unlikely(rc != 0)) { + CERROR("%s: can't update %s file: rc = %d\n", + tgt_name(tgt), REPLY_DATA, rc); + RETURN(rc); + } + } + /* add reply data to target export's reply list */ + mutex_lock(&ted->ted_lcd_lock); + list_add(&trd->trd_list, &ted->ted_reply_list); + ted->ted_reply_cnt++; + if (ted->ted_reply_cnt > ted->ted_reply_max) + ted->ted_reply_max = ted->ted_reply_cnt; + mutex_unlock(&ted->ted_lcd_lock); + + CDEBUG(D_TRACE, "add reply %p: xid %llu, transno %llu, " + "tag %hu, client gen %u, slot idx %d\n", + trd, lrd->lrd_xid, lrd->lrd_transno, + trd->trd_tag, lrd->lrd_client_gen, i); + RETURN(0); +} +EXPORT_SYMBOL(tgt_add_reply_data); + /* * last_rcvd & last_committed update callbacks */ @@ -1068,6 +1120,8 @@ static int tgt_last_rcvd_update(const struct lu_env *env, struct lu_target *tgt, struct thandle *th, struct ptlrpc_request *req) { struct tgt_thread_info *tti = tgt_th_info(env); + struct tgt_session_info *tsi = tgt_ses_info(env); + struct obd_export *exp = tsi->tsi_exp; struct tg_export_data *ted; __u64 *transno_p; int rc = 0; @@ -1075,15 +1129,21 @@ static int tgt_last_rcvd_update(const struct lu_env *env, struct lu_target *tgt, ENTRY; - ted = &req->rq_export->exp_target_data; - lw_client = exp_connect_flags(req->rq_export) & OBD_CONNECT_LIGHTWEIGHT; + LASSERT(exp != NULL); + ted = &exp->exp_target_data; + + lw_client = exp_connect_flags(exp) & OBD_CONNECT_LIGHTWEIGHT; if (ted->ted_lr_idx < 0 && !lw_client) /* ofd connect may cause transaction before export has * last_rcvd slot */ RETURN(0); - tti->tti_transno = lustre_msg_get_transno(req->rq_reqmsg); + if (req != NULL) + tti->tti_transno = lustre_msg_get_transno(req->rq_reqmsg); + else + /* From update replay, tti_transno should be set already */ + LASSERT(tti->tti_transno != 0); spin_lock(&tgt->lut_translock); if (th->th_result != 0) { @@ -1110,12 +1170,13 @@ static int tgt_last_rcvd_update(const struct lu_env *env, struct lu_target *tgt, CDEBUG(D_INODE, "transno = "LPU64", last_committed = "LPU64"\n", tti->tti_transno, tgt->lut_obd->obd_last_committed); - req->rq_transno = tti->tti_transno; - lustre_msg_set_transno(req->rq_repmsg, tti->tti_transno); + if (req != NULL) { + req->rq_transno = tti->tti_transno; + lustre_msg_set_transno(req->rq_repmsg, tti->tti_transno); + } /* if can't add callback, do sync write */ - th->th_sync |= !!tgt_last_commit_cb_add(th, tgt, req->rq_export, - tti->tti_transno); + th->th_sync |= !!tgt_last_commit_cb_add(th, tgt, exp, tti->tti_transno); if (lw_client) { /* All operations performed by LW clients are synchronous and @@ -1140,37 +1201,42 @@ static int tgt_last_rcvd_update(const struct lu_env *env, struct lu_target *tgt, * slot, update server data with latest transno then */ if (ted->ted_lcd == NULL) { CWARN("commit transaction for disconnected client %s: rc %d\n", - req->rq_export->exp_client_uuid.uuid, rc); + exp->exp_client_uuid.uuid, rc); GOTO(srv_update, rc = 0); } /* Target that supports multiple reply data */ - if (tgt_is_multimodrpcs_client(req->rq_export)) { + if (tgt_is_multimodrpcs_client(exp)) { struct tg_reply_data *trd; struct lsd_reply_data *lrd; __u64 *pre_versions; - int i; - loff_t off; + bool write_update; OBD_ALLOC_PTR(trd); if (unlikely(trd == NULL)) GOTO(srv_update, rc = -ENOMEM); - /* update export last transno */ - mutex_lock(&ted->ted_lcd_lock); - if (tti->tti_transno > ted->ted_lcd->lcd_last_transno) - ted->ted_lcd->lcd_last_transno = tti->tti_transno; - mutex_unlock(&ted->ted_lcd_lock); - /* fill reply data information */ lrd = &trd->trd_reply; lrd->lrd_transno = tti->tti_transno; - lrd->lrd_xid = req->rq_xid; - lrd->lrd_result = th->th_result; + if (req != NULL) { + lrd->lrd_xid = req->rq_xid; + trd->trd_tag = lustre_msg_get_tag(req->rq_reqmsg); + pre_versions = lustre_msg_get_versions(req->rq_repmsg); + lrd->lrd_result = th->th_result; + lrd->lrd_client_gen = ted->ted_lcd->lcd_generation; + write_update = true; + } else { + LASSERT(tsi->tsi_xid != 0); + lrd->lrd_xid = tsi->tsi_xid; + lrd->lrd_result = tsi->tsi_result; + lrd->lrd_client_gen = tsi->tsi_client_gen; + trd->trd_tag = 0; + pre_versions = NULL; + write_update = false; + } + lrd->lrd_data = opdata; - lrd->lrd_client_gen = ted->ted_lcd->lcd_generation; - trd->trd_tag = lustre_msg_get_tag(req->rq_reqmsg); - pre_versions = lustre_msg_get_versions(req->rq_repmsg); if (pre_versions) { trd->trd_pre_versions[0] = pre_versions[0]; trd->trd_pre_versions[1] = pre_versions[1]; @@ -1178,40 +1244,14 @@ static int tgt_last_rcvd_update(const struct lu_env *env, struct lu_target *tgt, trd->trd_pre_versions[3] = pre_versions[3]; } - /* find a empty slot */ - i = tgt_find_free_reply_slot(tgt); - if (unlikely(i < 0)) { - CERROR("%s: couldn't find a slot for reply data: " - "rc = %d\n", tgt_name(tgt), i); - GOTO(srv_update, rc = i); - } - trd->trd_index = i; - - /* write reply data to disk */ - off = sizeof(struct lsd_reply_header) + sizeof(*lrd) * i; - rc = tgt_reply_data_write(env, tgt, lrd, off, th); - if (unlikely(rc != 0)) { - CERROR("%s: can't update %s file: rc = %d\n", - tgt_name(tgt), REPLY_DATA, rc); - RETURN(rc); - } - - /* add reply data to target export's reply list */ - mutex_lock(&ted->ted_lcd_lock); - list_add(&trd->trd_list, &ted->ted_reply_list); - ted->ted_reply_cnt++; - if (ted->ted_reply_cnt > ted->ted_reply_max) - ted->ted_reply_max = ted->ted_reply_cnt; - mutex_unlock(&ted->ted_lcd_lock); - - CDEBUG(D_TRACE, "add reply %p: xid %llu, transno %llu, " - "tag %hu, client gen %u, slot idx %d\n", - trd, lrd->lrd_xid, lrd->lrd_transno, - trd->trd_tag, lrd->lrd_client_gen, i); - - GOTO(srv_update, rc = 0); + rc = tgt_add_reply_data(env, tgt, ted, trd, th, write_update); + GOTO(srv_update, rc); } + /* Enough for update replay, let's return */ + if (req == NULL) + GOTO(srv_update, rc); + mutex_lock(&ted->ted_lcd_lock); LASSERT(ergo(tti->tti_transno == 0, th->th_result != 0)); if (lustre_msg_get_opc(req->rq_reqmsg) == MDS_CLOSE) { @@ -1405,7 +1445,6 @@ static int tgt_clients_data_init(const struct lu_env *env, lcd->lcd_generation != 0) { /* compute the highest valid client generation */ generation = max(generation, lcd->lcd_generation); - /* fill client_generation <-> export hash table */ rc = cfs_hash_add_unique(hash, &lcd->lcd_generation, &exp->exp_gen_hash); @@ -1701,7 +1740,7 @@ int tgt_txn_stop_cb(const struct lu_env *env, struct thandle *th, if (tsi->tsi_exp == NULL) return 0; - echo_client = (tgt_ses_req(tsi) == NULL); + echo_client = (tgt_ses_req(tsi) == NULL && tsi->tsi_xid == 0); if (tti->tti_has_trans && !echo_client) { if (tti->tti_mult_trans == 0) { @@ -1742,7 +1781,6 @@ int tgt_reply_data_init(const struct lu_env *env, struct lu_target *tgt) loff_t off; struct cfs_hash *hash = NULL; struct obd_export *exp; - struct obd_export *tmp; struct tg_export_data *ted; int reply_data_recovered = 0; @@ -1855,20 +1893,6 @@ int tgt_reply_data_init(const struct lu_env *env, struct lu_target *tgt) } CDEBUG(D_INFO, "%s: %d reply data have been recovered\n", tgt_name(tgt), reply_data_recovered); - - /* delete entries from client_generation<->export hash */ - spin_lock(&tgt->lut_obd->obd_dev_lock); - list_for_each_entry_safe(exp, tmp, - &tgt->lut_obd->obd_exports, - exp_obd_chain) { - struct tg_export_data *ted = &exp->exp_target_data; - - if (!hlist_unhashed(&exp->exp_gen_hash)) - cfs_hash_del(hash, - &ted->ted_lcd->lcd_generation, - &exp->exp_gen_hash); - } - spin_unlock(&tgt->lut_obd->obd_dev_lock); } spin_lock(&tgt->lut_translock); @@ -1891,25 +1915,39 @@ out: return rc; } +struct tg_reply_data *tgt_lookup_reply_by_xid(struct tg_export_data *ted, + __u64 xid) +{ + struct tg_reply_data *found = NULL; + struct tg_reply_data *reply; + + mutex_lock(&ted->ted_lcd_lock); + list_for_each_entry(reply, &ted->ted_reply_list, trd_list) { + if (reply->trd_reply.lrd_xid == xid) { + found = reply; + break; + } + } + mutex_unlock(&ted->ted_lcd_lock); + return found; +} +EXPORT_SYMBOL(tgt_lookup_reply_by_xid); + /* Look for a reply data matching specified request @req * A copy is returned in @trd if the pointer is not NULL */ bool tgt_lookup_reply(struct ptlrpc_request *req, struct tg_reply_data *trd) { struct tg_export_data *ted = &req->rq_export->exp_target_data; - struct tg_reply_data *reply, *tmp; + struct tg_reply_data *reply; bool found = false; - mutex_lock(&ted->ted_lcd_lock); - list_for_each_entry_safe(reply, tmp, &ted->ted_reply_list, trd_list) { - if (reply->trd_reply.lrd_xid == req->rq_xid) { - found = true; - break; - } + reply = tgt_lookup_reply_by_xid(ted, req->rq_xid); + if (reply != NULL) { + found = true; + if (trd != NULL) + *trd = *reply; } - if (found && trd != NULL) - *trd = *reply; - mutex_unlock(&ted->ted_lcd_lock); CDEBUG(D_TRACE, "%s: lookup reply xid %llu, found %d\n", tgt_name(class_exp2tgt(req->rq_export)), req->rq_xid, diff --git a/lustre/target/update_recovery.c b/lustre/target/update_recovery.c index 5f121d2..8d7f5cc 100644 --- a/lustre/target/update_recovery.c +++ b/lustre/target/update_recovery.c @@ -1009,6 +1009,71 @@ static int update_recovery_xattr_del(const struct lu_env *env, } /** + * Update session information + * + * Update session information so tgt_txn_stop_cb()->tgt_last_rcvd_update() + * can be called correctly during update replay. + * + * \param[in] env execution environment. + * \param[in] tdtd distribute data structure of the recovering tgt. + * \param[in] th thandle of this update replay. + * \param[in] master_th master sub thandle. + * \param[in] ta_arg the tx arg structure to hold the update for updating + * reply data. + */ +static void update_recovery_update_ses(struct lu_env *env, + struct target_distribute_txn_data *tdtd, + struct thandle *th, + struct thandle *master_th, + struct tx_arg *ta_arg) +{ + struct tgt_session_info *tsi; + struct lu_target *lut = tdtd->tdtd_lut; + struct obd_export *export; + struct cfs_hash *hash; + struct top_thandle *top_th; + struct lsd_reply_data *lrd; + size_t size; + + tsi = tgt_ses_info(env); + if (tsi->tsi_exp != NULL) + return; + + size = ta_arg->u.write.buf.lb_len; + lrd = ta_arg->u.write.buf.lb_buf; + if (size != sizeof(*lrd) || lrd == NULL) + return; + + lrd->lrd_transno = le64_to_cpu(lrd->lrd_transno); + lrd->lrd_xid = le64_to_cpu(lrd->lrd_xid); + lrd->lrd_data = le64_to_cpu(lrd->lrd_data); + lrd->lrd_result = le32_to_cpu(lrd->lrd_result); + lrd->lrd_client_gen = le32_to_cpu(lrd->lrd_client_gen); + + if (lrd->lrd_transno != tgt_th_info(env)->tti_transno) + return; + + hash = cfs_hash_getref(lut->lut_obd->obd_gen_hash); + if (hash == NULL) + return; + + export = cfs_hash_lookup(hash, &lrd->lrd_client_gen); + if (export == NULL) { + cfs_hash_putref(hash); + return; + } + + tsi->tsi_exp = export; + tsi->tsi_xid = lrd->lrd_xid; + tsi->tsi_opdata = lrd->lrd_data; + tsi->tsi_result = lrd->lrd_result; + tsi->tsi_client_gen = lrd->lrd_client_gen; + top_th = container_of(th, struct top_thandle, tt_super); + top_th->tt_master_sub_thandle = master_th; + cfs_hash_putref(hash); +} + +/** * Execute updates in the update replay records * * Declare distribute txn replay by update records and add the updates @@ -1213,6 +1278,7 @@ int distribute_txn_replay_handle(struct lu_env *env, if (rc < 0) GOTO(stop_trans, rc); + th->th_dev = tdtd->tdtd_dt; ta->ta_handle = th; /* check if the distribute transaction has been committed */ @@ -1228,10 +1294,9 @@ int distribute_txn_replay_handle(struct lu_env *env, if (rc < 0) GOTO(stop_trans, rc); - /* If no updates are needed to be replayed, then - * mark this records as committed, so commit thread - * distribute_txn_commit_thread() will delete the - * record */ + /* If no updates are needed to be replayed, then mark this records as + * committed, so commit thread distribute_txn_commit_thread() will + * delete the record */ if (ta->ta_argno == 0) tmt->tmt_committed = 1; @@ -1259,10 +1324,19 @@ int distribute_txn_replay_handle(struct lu_env *env, LASSERT(tmt->tmt_committed == 0); sub_dt = lu2dt_dev(dt_obj->do_lu.lo_dev); st = lookup_sub_thandle(tmt, sub_dt); + LASSERT(st != NULL); LASSERT(st->st_sub_th != NULL); rc = ta->ta_args[i]->exec_fn(env, st->st_sub_th, ta->ta_args[i]); + + /* If the update is to update the reply data, then + * we need set the session information, so + * tgt_last_rcvd_update() can be called correctly */ + if (rc == 0 && dt_obj == tdtd->tdtd_lut->lut_reply_data) + update_recovery_update_ses(env, tdtd, th, + st->st_sub_th, ta_arg); + if (unlikely(rc < 0)) { CDEBUG(D_HA, "error during execution of #%u from" " %s:%d: rc = %d\n", i, ta->ta_args[i]->file, @@ -1305,6 +1379,11 @@ stop_trans: if (tur != NULL) tur->tur_update_records = NULL; + + if (tgt_ses_info(env)->tsi_exp != NULL) { + class_export_put(tgt_ses_info(env)->tsi_exp); + tgt_ses_info(env)->tsi_exp = NULL; + } exit_session: lu_context_exit(&session_env); lu_context_fini(&session_env); -- 1.8.3.1