merge job rusage and pending reason support

This commit is contained in:
Rongsong Shen 2025-12-16 14:36:28 +08:00
parent 30824ae5ea
commit cb0c7fe9e7
82 changed files with 4280 additions and 2561 deletions

View file

@ -5,6 +5,7 @@
#include <unistd.h>
#include "cluster.h"
#include "dataq.h"
#include "htab.h"
#include "mylock.h"
#include "request.h"
@ -188,6 +189,12 @@ static int myreq_handler(vtask_t* task)
case OPCode_runtasks:
rc = do_runtasks(c, ep, &request);
break;
case OPCode_setpreason:
rc = do_set_preason(c, ep, &request);
break;
case OPCode_clrpreason:
rc = do_clr_preason(c, ep, &request);
break;
default:
rc = -E_XCE_BAD_OPCODE;
ERROR("Unknown opcode: %d", request.opcode);
@ -447,6 +454,11 @@ static int xce_install_hooks(cluster_t* c)
return 0;
}
static int run_delay_tasks(delay_task_entry_fn_t entry, void* arg)
{
return entry(arg);
}
int cluster_init(cluster_t* c)
{
int i, rc;
@ -524,12 +536,90 @@ int cluster_init(cluster_t* c)
vtaskmgr_init(&(c->vtaskmgr), c->myconf->cluster_config->vcpus);
delaymgr_init(&(c->delaymgr), 2, run_delay_tasks);
xce_run_hooks(&(c->hooksys), XCE_CONTROLLER_HOOK_CLUSTER, HOOK_AFTER_EVENT,
HOOK_DETAIL_CLUSTER_INIT, c);
return 0;
}
static int make_heartbeat(cluster_t* c, endpoint_t* ep)
{
return xce_nop_req(ep);
}
static int check_worker_timeout_(void* arg1, void* arg2)
{
cluster_t* c = (cluster_t*)arg1;
worker_t* w = (worker_t*)arg2;
endpoint_t* ep;
time_t now = time(NULL);
int timeout;
cluster_config_t* config;
if (w == NULL) {
return 0;
}
if (c == NULL) {
return -EINVAL;
}
ep = w->ep;
if (ep == NULL) {
return 0;
}
config = c->myconf->cluster_config;
timeout = config->heartbeat_interval * config->inactive_heartbeat_count;
if (now - ep->ts_last_in > timeout) {
TRACE("%s worker %s becomes UNAVAIL as heartbeat timeout", __func__,
w->host.name);
mark_worker_unavail(c, w);
return 0;
} else {
if (IS_WORKER_UNAVAIL(w->host.status)) {
mark_worker_back(c, w);
}
}
if (now - ep->ts_last_out >= config->heartbeat_interval) {
if (DATAQ_EMPTY(&(ep->outq))) {
int rc = 0;
TRACE("%s send heartbeat to %s", __func__, w->host.name);
rc = make_heartbeat(c, ep);
if (rc < 0) {
ERROR("%s make_heartbeat to %s failed", __func__, w->host.name);
return rc;
}
}
}
return 0;
}
static int check_workers_timeout(void* args)
{
xlist_t lst;
int rc;
cluster_t* c = (cluster_t*)args;
xlist_init(&lst, 3);
xlist_store(&lst, check_worker_timeout_);
rc = workermgr_iter(&(c->workermgr), &lst, c);
xlist_fini(&lst);
return rc;
}
int cluster_start(cluster_t* c)
{
int rc;
@ -546,6 +636,10 @@ int cluster_start(cluster_t* c)
iomgr_start(&(c->iomgr));
vtaskmgr_start(&(c->vtaskmgr));
delaymgr_start(&(c->delaymgr));
delay_task(&(c->delaymgr), c->myconf->cluster_config->heartbeat_interval,
-1, (delay_task_entry_fn_t)check_workers_timeout, (void*)c);
c->running = 1;
return 0;
@ -565,6 +659,9 @@ int cluster_run(cluster_t* c)
xce_run_hooks(&(c->hooksys), XCE_CONTROLLER_HOOK_CLUSTER, HOOK_BEFORE_EVENT,
HOOK_DETAIL_CLUSTER_SHUTDOWN, c);
c->running = 0;
delaymgr_stop(&(c->delaymgr));
iomgr_unmonitor(&(c->iomgr), &(c->ep));
iomgr_stop(&(c->iomgr));
@ -573,6 +670,7 @@ int cluster_run(cluster_t* c)
void cluster_fini(cluster_t* c)
{
delaymgr_fini(&(c->delaymgr));
vtaskmgr_fini(&(c->vtaskmgr));
endpoint_fini(&(c->ep));

View file

@ -98,6 +98,8 @@ extern int handle_reply(endpoint_t*, struct Request*, cluster_t*);
extern int handle_jobdispatch(cluster_t*, endpoint_t*, xce_request_t*);
extern int handle_jobsubmit(cluster_t*, endpoint_t*, xce_request_t*);
extern int handle_worker_leave(cluster_t*, int);
extern int mark_worker_unavail(cluster_t*, worker_t*);
extern int mark_worker_back(cluster_t*, worker_t*);
extern int handle_scheduler_leave(cluster_t*);
extern int handle_clusterconf(cluster_t*, endpoint_t*, xce_request_t*);
@ -151,5 +153,7 @@ extern int xce_controller_res_init(cluster_t*);
extern int map_resource_to_host(char*, cluster_t*);
extern int release_resource_from_worker(char*, cluster_t*);
extern int collect_resval_with_idx(zarray_t*, int*, xce_val_with_idx_t***);
extern int do_set_preason(cluster_t*, endpoint_t*, xce_request_t*);
extern int do_clr_preason(cluster_t*, endpoint_t*, xce_request_t*);
#endif

View file

@ -3,6 +3,7 @@
#include <time.h>
#include "cluster.h"
#include "preason.h"
#include "xce/cluster_def.h"
#include "xce/errnox.h"
#include "xce/types.h"
@ -457,7 +458,10 @@ int handle_jobsubmit(cluster_t* c, endpoint_t* ep, xce_request_t* request)
/* Run hooks to tell scheduler */
j = jobmgr_lockjob(&(c->jobmgr), jobid, OBJREF_READ);
if (j == NULL) {
ERROR("%s fail to lock job %lu", __func__, jobid);
char idstring[128];
fmt_jobid(idstring, sizeof(idstring) - 1, jobid);
ERROR("%s fail to lock job %s (%lu)", __func__, idstring, jobid);
} else {
xlist_init(&jlist, 2);
if (xlist_store(&jlist, j) >= 0) {
@ -777,6 +781,9 @@ int update_job_rusages(cluster_t* c, endpoint_t* ep, xce_request_t* request)
TRACE("%s trying to update job(%ld) rusage", __func__, rusages->jobid);
xce_run_hooks(&(c->hooksys), XCE_CONTROLLER_HOOK_JOB, HOOK_BEFORE_EVENT,
HOOK_DETAIL_JOB_RUSAGE_UPDATE, c, rusages);
j = jobmgr_lockjob(&(c->jobmgr), rusages->jobid, OBJREF_UPDATE);
if (j == NULL) {
PERROR("jobmgr_lockjob");
@ -815,5 +822,106 @@ int update_job_rusages(cluster_t* c, endpoint_t* ep, xce_request_t* request)
HOOK_DETAIL_JOB_UPDATE, c, j);
obj_deref(j);
xce_run_hooks(&(c->hooksys), XCE_CONTROLLER_HOOK_JOB, HOOK_AFTER_EVENT,
HOOK_DETAIL_JOB_RUSAGE_UPDATE, c, rusages);
return 0;
}
static int set_preason(cluster_t* c, xce_pendreason_t* reasons)
{
job_t* j;
xce_job_t* job;
TRACE("%s jobid %ld", __func__, reasons->jobid);
j = jobmgr_lockjob(&(c->jobmgr), reasons->jobid, OBJREF_UPDATE);
if (j == NULL) {
PERROR("jobmgr_lockjob");
return -ENOENT;
}
xce_run_hooks(&(c->hooksys), XCE_CONTROLLER_HOOK_JOB, HOOK_BEFORE_EVENT,
HOOK_DETAIL_SETPREASON, c, j);
job = &(j->job);
if (job->pend_reasons != NULL) {
erase_job_preason(job->pend_reasons);
FREEUP(job->pend_reasons);
}
MOVE_PTR(job->pend_reasons, reasons->reasons);
xce_run_hooks(&(c->hooksys), XCE_CONTROLLER_HOOK_JOB, HOOK_AFTER_EVENT,
HOOK_DETAIL_SETPREASON, c, j);
obj_deref(j);
return 0;
}
int do_set_preason(cluster_t* c, endpoint_t* ep, xce_request_t* request)
{
int i;
xce_preason_set_t* reasons = request->body.preasonmsg;
xce_reply_t reply;
init_reply_by_req(&reply, request);
reply.status = 0;
reply.data_which = RequestReply_data_nodata;
xce_reply(ep, c->myhost, &reply);
for (i = 0; i < reasons->nreasons; i++) {
int rc = 0;
if (reasons->reasons[i] == NULL) {
continue;
}
rc = set_preason(c, reasons->reasons[i]);
if (rc < 0) {
PERROR("set_preason");
}
}
return 0;
}
int do_clr_preason(cluster_t* c, endpoint_t* ep, xce_request_t* request)
{
int i;
xce_reply_t reply;
xce_clear_reason_t* clreason = request->body.clrpreasonmsg;
init_reply_by_req(&reply, request);
reply.status = 0;
reply.data_which = RequestReply_data_nodata;
xce_reply(ep, c->myhost, &reply);
for (i = 0; i < clreason->njobs; i++) {
uint64_t jobid = clreason->jobs[i];
job_t* j;
xce_job_t* job;
j = jobmgr_lockjob(&(c->jobmgr), jobid, OBJREF_UPDATE);
if (j == NULL) {
PERROR("jobmgr_lockjob");
continue;
}
xce_run_hooks(&(c->hooksys), XCE_CONTROLLER_HOOK_JOB, HOOK_BEFORE_EVENT,
HOOK_DETAIL_CLRPREASON, c, j);
job = &(j->job);
if (job->pend_reasons != NULL) {
erase_job_preason(job->pend_reasons);
FREEUP(job->pend_reasons);
}
xce_run_hooks(&(c->hooksys), XCE_CONTROLLER_HOOK_JOB, HOOK_BEFORE_EVENT,
HOOK_DETAIL_CLRPREASON, c, j);
obj_deref(j);
}
return 0;
}

View file

@ -143,10 +143,28 @@ static int do_reply_joblist(cluster_t* c, endpoint_t* ep,
xce_data_t getdata = { .table = request->body.getmsg->table,
.data = { .jobs = &jobdata } };
int rc = 0;
xce_match_t* match = request->body.getmsg->condition;
init_reply_by_req(&reply, request);
collect_jobs(c, (collect_check_fn_t)check_job_, &(jobdata.n_total),
&(jobdata.jobs));
if (match->type == MatchCondition_condition_matchjid) {
job_t* j;
jobdata.n_total = 0;
jobdata.jobs = NULL;
j = jobmgr_lockjob(&(c->jobmgr), match->condition.jobid, OBJREF_READ);
if (j != NULL) {
jobdata.jobs = (xce_job_t**)calloc(1, sizeof(xce_job_t*));
if (jobdata.jobs != NULL) {
jobdata.jobs[0] = &(j->job);
jobdata.n_total = 1;
}
}
} else {
collect_jobs(c, (collect_check_fn_t)check_job_, &(jobdata.n_total),
&(jobdata.jobs));
}
reply.status = 0;
reply.data_which = RequestReply_data_getdata;

View file

@ -314,6 +314,37 @@ static int sched_job_update_fun(int type, int situation, void* data, va_list ap)
return rc;
}
static int sched_job_rusage_update_fun(int type, int situation, void* data,
va_list ap)
{
cluster_t* c = va_arg(ap, cluster_t*);
xce_job_rusages_t* rusages = va_arg(ap, xce_job_rusages_t*);
int rc = 0;
/* clang-format off */
xce_notify_data_t xdata = {
.event = NotifyData_data_schedJobRusages,
.data = {
.sched_job_rusages = rusages
}
};
/* clang-format on */
mylock_lock(&(c->sched.lock));
if (c->sched.ep != NULL) {
switch (situation) {
case HOOK_AFTER_EVENT:
rc = notify(c, c->sched.ep, &xdata);
break;
default:
break;
}
}
mylock_unlock(&(c->sched.lock));
return rc;
}
static int sched_job_hook_fun(int type, int situation, int detail, void* data,
va_list ap)
{
@ -328,6 +359,8 @@ static int sched_job_hook_fun(int type, int situation, int detail, void* data,
return sched_job_update_fun(type, situation, data, ap);
case HOOK_DETAIL_JOB_FAIL:
return sched_job_fail_fun(type, situation, data, ap);
case HOOK_DETAIL_JOB_RUSAGE_UPDATE:
return sched_job_rusage_update_fun(type, situation, data, ap);
}
return 0;

View file

@ -51,6 +51,8 @@ static int check_task_hosts(cluster_t* c, xce_taskrun_t* runmsg)
obj_deref(w);
return -ENOENT;
}
obj_deref(w);
}
return 0;

View file

@ -17,7 +17,9 @@
#include "client.h"
#include "controller.capnp.h"
#include "delay.h"
#include "idgen.h"
#include "preason.h"
#include "xce/types.h"
typedef int (*cmd_func_t)(int, char*[]);
@ -73,6 +75,7 @@ typedef struct {
#define MAX_TASK_HOSTS (5)
#define MAX_RUN_TASKS (50)
#define RUSAGE_UPDATE_INTERVAL (60)
#define MAX_LINE_LEN (40960)
#define S2J_JSON_SET_var_string_ELEMENT(to_json, from_struct, _element) \
if (((from_struct) != NULL) && (((from_struct)->_element) != NULL)) { \
@ -109,14 +112,12 @@ extern int cmd_jobkill(int, char*[]);
extern int cmd_jobckpt(int, char*[]);
extern int cmd_qctrl(int, char*[]);
extern int cmd_runtasks(int, char*[]);
extern int cmd_setpend(int, char*[]);
extern int cmd_jobinfo(int, char*[]);
static int worker_house_keeping(void*);
static void handler(int, siginfo_t*, void*);
#define CLOCKID CLOCK_REALTIME
#define SIG SIGRTMIN
static timer_t timerid;
static idgen_ctx_t idctx;
delaymgr_t delaymgr;
static setting_t settings[] = {
{ (char*)"host", (char*)NULL, (char*)"localhost" },
@ -168,6 +169,7 @@ static command_t client_commands[] = {
{ "jobsub", cmd_jobsub, "submit a job" },
{ "jobkill", cmd_jobkill, "signal a job" },
{ "jobckpt", cmd_jobckpt, "checkpoint a job" },
{ "jobinfo", cmd_jobinfo, "job information" },
{ "resval", cmd_resval, "get value of res" },
{ "taskrun", cmd_runtasks, "run tasks" },
{ "help", cmd_help, "Display this text" },
@ -186,6 +188,7 @@ static command_t scheduler_commands[] = {
{ "quit", cmd_quit, "Quit xce test" },
{ "join", cmd_join_sched, "Join the cluster" },
{ "getconf", cmd_getconf, "Get conf of cluster" },
{ "setpend", cmd_setpend, "set job pend reason" },
{ (char*)NULL, (cmd_func_t)NULL, (char*)NULL }
};
@ -278,7 +281,7 @@ int leave_ctx(command_ctx_t* ctx)
}
if (ctx == &workerctx) {
timer_delete(timerid);
delaymgr_fini(&delaymgr);
}
xcecli_wakeup(active_ctx->ctx, (int)-EINTR);
@ -392,10 +395,7 @@ int cmd_up(int argc, char* argv[])
int cmd_worker(int argc, char* argv[])
{
int rc;
struct sigevent sev;
struct sigaction sa;
struct itimerspec its;
int rc;
if ((rc = enter_ctx(&workerctx)) < 0) {
return rc;
@ -403,22 +403,10 @@ int cmd_worker(int argc, char* argv[])
active_ctx = &workerctx;
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = handler;
sigemptyset(&(sa.sa_mask));
sigaction(SIG, &sa, NULL);
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIG;
sev.sigev_value.sival_ptr = &timerid;
timer_create(CLOCKID, &sev, &timerid);
memset(&its, 0x0, sizeof(its));
its.it_value.tv_sec = 1;
timer_settime(timerid, 0, &its, NULL);
delaymgr_init(&delaymgr, 2, NULL);
delaymgr_start(&delaymgr);
delay_task(&delaymgr, 5, -1, (delay_task_entry_fn_t)worker_house_keeping,
&workerctx);
return 0;
}
@ -631,11 +619,94 @@ int cmd_joblist(int argc, char* argv[])
jobs[i]->submit->start_info->cmdline);
}
for (i = 0; i < rc; i++) {
free_Job_ptr(&(jobs[i]));
}
free(jobs);
return 0;
}
static void print_jobinfo(xce_job_t* job)
{
char idstring[128];
if (job == NULL) {
return;
}
fmt_jobid(idstring, sizeof(idstring) - 1, job->jobid);
printf("JobID: %s\n", idstring);
printf("state: %s\n", job_state_str(job->state));
printf("Queue: %s\n", job->submit->queue);
printf("User: %s\n", job->submit->user);
printf("Slots: %d,%d\n", job->submit->mintasks, job->submit->maxtasks);
printf("Command: %s\n", job->submit->start_info->cmdline);
if (job->pend_reasons != NULL) {
printf("Pend Reason:\n");
if (job->pend_reasons->main_reason != 0) {
printf("\tmain Reason: %d\n", job->pend_reasons->main_reason);
} else {
if (job->pend_reasons->nhost_reasons > 0) {
int i;
printf("\tHost Reasons:\n");
for (i = 0; i < job->pend_reasons->nhost_reasons; i++) {
printf("\t\t(%s, %d)\n",
job->pend_reasons->host_based_reasons[i]->host,
job->pend_reasons->host_based_reasons[i]->reason);
}
}
if (job->pend_reasons->nres_reasons > 0) {
int i;
printf("\tResource Reasons:\n");
for (i = 0; i < job->pend_reasons->nres_reasons; i++) {
printf("\t\t(%s, %s, %d)\n",
job->pend_reasons->res_based_reasons[i]->resource,
job->pend_reasons->res_based_reasons[i]->host,
job->pend_reasons->res_based_reasons[i]->reason);
}
}
}
}
}
int cmd_jobinfo(int argc, char* argv[])
{
int opt;
optind = 1;
while ((opt = getopt(argc, argv, "j:")) != -1) {
uint64_t jobid;
xce_job_t* job;
int rc = 0;
switch (opt) {
case 'j':
parse_jobid(&jobid, optarg);
if (jobid > 0) {
rc = xcecli_jobinfo(active_ctx->ctx, jobid, &job);
if (rc < 0) {
perror("xcecli_jobinfo");
continue;
}
print_jobinfo(job);
free_Job_ptr(&job);
}
break;
}
}
return 0;
}
static int fill_job_alloc(xce_job_alloc_t* jalloc, int nhosts,
host_task_t* hosts)
{
@ -1256,6 +1327,91 @@ int cmd_runtasks(int argc, char* argv[])
return rc;
}
static int add_pendreason(xce_job_preason_t* preason, char* s)
{
char s1[MAX_LINE_LEN];
char* pcode = NULL;
char* phost = NULL;
char* pres = NULL;
char* p;
int code;
if ((preason == NULL) || (s == NULL)) {
fprintf(stderr, "bad parameter\n");
return -1;
}
if ((p = strrchr(s, (int)':')) == NULL) {
pcode = s;
} else {
pcode = p + 1;
memset(s1, 0x0, sizeof(s1));
strncpy(s1, s, p - s);
if ((p = strrchr(s1, (int)':')) == NULL) {
phost = s1;
} else {
*p = 0x0;
phost = p + 1;
pres = s1;
}
}
code = atoi(pcode);
return add_job_preason(preason, code, phost, pres);
}
int cmd_setpend(int argc, char* argv[])
{
int opt;
xce_job_preason_t jreasons;
xce_pendreason_t preason = { .reasons = &jreasons };
xce_preason_set_t reasons
= { .nreasons = 1, .reasons = (xce_pendreason_t*[]) { &preason } };
int rc = 0;
optind = 1;
while ((opt = getopt(argc, argv, "j:p:")) != -1) {
int rc = 0;
switch (opt) {
case 'j':
parse_jobid(&(preason.jobid), optarg);
break;
case 'p':
rc = add_pendreason(&jreasons, optarg);
if (rc < 0) {
perror("add_pendreason");
return rc;
}
break;
}
}
if (preason.jobid == 0) {
fprintf(stderr, "no or incorrect jobid specified\n");
return -1;
}
if ((jreasons.main_reason == 0) && (jreasons.nhost_reasons == 0)
&& (jreasons.nres_reasons == 0)) {
fprintf(stderr, "no or incorrect pend reason set\n");
return -1;
}
rc = xcecli_set_pend_reason(active_ctx->ctx, &reasons);
if (rc < 0) {
perror("xcecli_set_pend_reason");
erase_job_preason(&jreasons);
return rc;
}
printf("ok\n");
return 0;
}
int cmd_join_worker(int argc, char* argv[])
{
int rc;
@ -2263,18 +2419,24 @@ static void check_run_tasks()
}
}
static void handler(int sig, siginfo_t* si, void* uc)
#define HEARTBEAT_INTERVAL (60)
static int worker_house_keeping(void* arg)
{
struct itimerspec its;
command_ctx_t* ctx = (command_ctx_t*)arg;
int rc = 0;
xce_use(ctx);
check_run_jobs();
check_run_tasks();
memset(&its, 0x0, sizeof(its));
rc = xcecli_heartbeat(ctx->ctx, HEARTBEAT_INTERVAL);
if (rc < 0) {
perror("xcecli_heartbeat");
}
its.it_value.tv_sec = 1;
timer_settime(timerid, 0, &its, NULL);
return rc;
}
int main(int argc, char* argv[])

View file

@ -476,6 +476,28 @@ int get_resval(cluster_t* c, char* resname, char* host, xce_val_t* val)
return 1;
}
int mark_worker_unavail(cluster_t* c, worker_t* w)
{
xce_run_hooks(&(c->hooksys), XCE_CONTROLLER_HOOK_WORKER, HOOK_BEFORE_EVENT,
HOOK_DETAIL_WORKER_DEAD, c, w);
w->host.status |= XCE_WORKER_UNAVAIL;
xce_run_hooks(&(c->hooksys), XCE_CONTROLLER_HOOK_WORKER, HOOK_AFTER_EVENT,
HOOK_DETAIL_WORKER_DEAD, c, w);
return 0;
}
int mark_worker_back(cluster_t* c, worker_t* w)
{
xce_run_hooks(&(c->hooksys), XCE_CONTROLLER_HOOK_WORKER, HOOK_BEFORE_EVENT,
HOOK_DETAIL_WORKER_UPDATE, c, w);
w->host.status &= ~XCE_WORKER_UNAVAIL;
xce_run_hooks(&(c->hooksys), XCE_CONTROLLER_HOOK_WORKER, HOOK_AFTER_EVENT,
HOOK_DETAIL_WORKER_UPDATE, c, w);
return 0;
}
int handle_worker_leave(cluster_t* c, int idx)
{
worker_t* w = NULL;
@ -499,13 +521,8 @@ int handle_worker_leave(cluster_t* c, int idx)
return -errno;
}
xce_run_hooks(&(c->hooksys), XCE_CONTROLLER_HOOK_WORKER, HOOK_BEFORE_EVENT,
HOOK_DETAIL_WORKER_DEAD, c, w);
w->host.status |= XCE_WORKER_UNAVAIL;
w->ep = NULL;
mark_worker_unavail(c, w);
xce_run_hooks(&(c->hooksys), XCE_CONTROLLER_HOOK_WORKER, HOOK_AFTER_EVENT,
HOOK_DETAIL_WORKER_DEAD, c, w);
obj_deref(w);
return 0;
}
@ -539,7 +556,6 @@ int worker_ctrl(cluster_t* c, xce_hostctrl_t* ctrl)
}
w->host.status |= XCE_WORKER_CLOSED;
break;
break;
default:
rc = -1;
break;

View file

@ -5,6 +5,7 @@
#include "cluster.h"
#include "objmgr.h"
#include "preason.h"
#include "utils.h"
#include "xce/cluster_def.h"
#include "xstring.h"
@ -296,6 +297,11 @@ void job_obj_killer(job_t* job)
FREEUP(job->job.run_usage);
}
if (job->job.pend_reasons != NULL) {
erase_job_preason(job->job.pend_reasons);
FREEUP(job->job.pend_reasons);
}
FREEUP(job->job.exit_info);
memset(obj, 0x0, sizeof(*obj));